Найти в Дзене
Записки о Java

Как устроена Java-машина в Java 11: архитектура, память и эволюция от Java 8

Java 11 — это вторая LTS (Long-Term Support) версия после Java 8, и на сегодняшний день она остаётся одной из самых распространённых в production-средах. Понимание архитектуры JVM в Java 11 критически важно для разработчиков, отвечающих за производительность, стабильность и эффективное использование ресурсов. В этой статье мы: JVM остаётся виртуальной машиной, исполняющей байт-код, но её внутреннее устройство продолжает эволюционировать. Основные компоненты: Class Loader Subsystem Загружает, линкует и инициализирует классы Runtime Data Areas Области памяти (о них — основная часть статьи) Execution Engine Выполняет байт-код (интерпретатор, JIT-компилятор, GC) Native Method Interface (JNI) Взаимодействие с нативным кодом Native Libraries Системные библиотеки ОС Главные изменения в Java 9–11: Все области памяти делятся на потоковые (приватные для каждого потока) и общие (разделяемые между потоками). Назначение: хранение всех экземпляров объектов и массивов. +-----------------------------+
Оглавление

Введение

Java 11 — это вторая LTS (Long-Term Support) версия после Java 8, и на сегодняшний день она остаётся одной из самых распространённых в production-средах. Понимание архитектуры JVM в Java 11 критически важно для разработчиков, отвечающих за производительность, стабильность и эффективное использование ресурсов.

В этой статье мы:

  • детально разберём структуру памяти JVM в Java 11,
  • сравним её с Java 8,
  • покажем реальные примеры и типичные проблемы,
  • дадим рекомендации по тонкой настройке и диагностике.

1. Обзор архитектуры JVM в Java 11

JVM остаётся виртуальной машиной, исполняющей байт-код, но её внутреннее устройство продолжает эволюционировать. Основные компоненты:

Class Loader Subsystem

Загружает, линкует и инициализирует классы

Runtime Data Areas

Области памяти (о них — основная часть статьи)

Execution Engine

Выполняет байт-код (интерпретатор, JIT-компилятор, GC)

Native Method Interface (JNI)

Взаимодействие с нативным кодом

Native Libraries

Системные библиотеки ОС

Главные изменения в Java 9–11:

  • полное исчезновение PermGen (уже в Java 8),
  • появление Metaspace как замены,
  • модульная система JPMS (Project Jigsaw),
  • новые алгоритмы GC: ZGC (экспериментальный в Java 11), Shenandoah (в OpenJDK, но не в Oracle JDK по умолчанию),
  • улучшенная работа с off-heap памятью.

2. Области памяти JVM в Java 11

Все области памяти делятся на потоковые (приватные для каждого потока) и общие (разделяемые между потоками).

2.1. Heap (Куча) — общая память

Назначение: хранение всех экземпляров объектов и массивов.

Структура кучи (по умолчанию — Parallel GC):

+-----------------------------+

| Old Generation |

+-----------------------------+

| Young Generation |

| +--------+ +--------+ |

| | Eden | |Survivor| |

| +--------+ +--------+ |

| (S0 / S1) |

+-----------------------------+

🔍 В Java 11 поведение кучи не изменилось по сравнению с Java 8. Но доступны новые GC, которые кардинально меняют её организацию.

Пример:

public class HeapExample {

public void process() {

// Объект создаётся в Eden

StringBuilder sb = new StringBuilder("Hello");

sb.append(" World");

// Если sb "переживёт" Minor GC — попадёт в Survivor,

// затем — в Old Gen

}

}

Настройка:

-Xms2g # начальный размер кучи

-Xmx4g # максимальный размер

-XX:NewRatio=3 # соотношение Old:Young = 3:1

-XX:SurvivorRatio=8 # Eden:Survivor = 8:1 (на каждый Survivor)

2.2. Metaspace — замена PermGen

Где находится: в нативной памяти ОС, а не в куче JVM.

Что хранит:

  • метаданные классов (имена, методы, поля),
  • статические переменные (сами значения — в куче!),
  • константный пул (constant pool),
  • JIT-компиляторные структуры.
❗ Важно: в Java 8 Metaspace уже появился, но в Java 11 его управление стало ещё более гибким.

Пример утечки Metaspace:

// Динамическая генерация классов (например, через CGLib, Hibernate, Spring Proxy)

public class MetaspaceLeak {

public static void main(String[] args) throws Exception {

while (true) {

// Каждая итерация создаёт НОВЫЙ класс

// → MetaSpace растёт бесконечно

}

}

}

Диагностика:

# Включить логирование Metaspace

-XX:+PrintGCDetails

-XX:+TraceClassLoading

-XX:+TraceClassUnloading

Настройка:

-XX:MetaspaceSize=128m # триггер для первого GC в Metaspace

-XX:MaxMetaspaceSize=512m # жёсткий лимит

⚠️ Без MaxMetaspaceSize JVM может исчерпать всю виртуальную память ОС → java.lang.OutOfMemoryError: Metaspace.

2.3. Thread Stacks — приватная память

Каждый поток имеет свой стек. Хранит:

  • локальные переменные (примитивы и ссылки),
  • параметры методов,
  • return-адреса,
  • stack frames (по одному на вызов метода).

Пример:

public int factorial(int n) {

if (n <= 1) return 1;

return n * factorial(n - 1); // каждый вызов — новый frame

}

При factorial(10_000) → StackOverflowError.

Настройка:

-Xss1m # размер стека на поток (по умолчанию: 1M в 64-бит JVM)

💡 Уменьшайте -Xss, если приложение создаёт тысячи потоков (например, в reactive-системах лучше использовать виртуальные потоки — но они появились только в Java 21).

2.4. PC Register (Program Counter)

  • Указывает на адрес следующей инструкции байт-кода.
  • У каждого потока — свой.
  • Не потребляет значительной памяти.

2.5. Native Method Stack

Для методов, помеченных как native (через JNI). Использует стек ОС.

2.6. Code Cache

Новая важная область! Хотя она существовала и раньше, в Java 11 её роль возросла.

Назначение: хранит нативный код, сгенерированный JIT-компилятором (HotSpot).

  • Когда метод "горячий" (часто вызывается), JIT компилирует его в native-код.
  • Этот код кэшируется в Code Cache.

Проблема:

Если Code Cache переполняется → JIT отключается, и производительность падает.

Настройка:

-XX:ReservedCodeCacheSize=240m # по умолчанию ~240M

-XX:+PrintCodeCache # логировать использование

3. Garbage Collection в Java 11: что изменилось?

Доступные GC-алгоритмы в Java 11:

Serial GC

Встроен

Для одноядерных систем, маленьких heaps

Parallel GC

По умолчанию

Высокая throughput, но остановки

CMS

Удалён в Java 14, но ещё есть в 11

Low-latency, но deprecated

G1 GC

Рекомендуется для heaps > 4 ГБ

Предсказуемые паузы

ZGC

Экспериментальный(включается флагом)

Паузы < 10 мс, heaps до TB

Shenandoah

В OpenJDK (не в Oracle JDK)

Low-pause, concurrent GC

Включить G1 (часто используется в продакшене):

-XX:+UseG1GC

Включить ZGC (требует -XX:+UnlockExperimentalVMOptions в Java 11):

-XX:+UnlockExperimentalVMOptions

-XX:+UseZGC

4. Отличия Java 11 от Java 8 (кратко)

PermGen

Есть (java 8)

Полностью удалён (java 11)

Metaspace

Введён (java 8)

Улучшен, стабилен (java 11)

GC по умолчанию

Parallel (java 8)

Parallel (но G1 активно рекомендуется) (java 11)

Новые GC

Нет (java 8)

ZGC (эксперимент), Shenandoah (OpenJDK) (java 11)

Модульность

Нет (java 8)

JPMS (Project Jigsaw) — влияет на ClassLoader (java 11)

JRE/JDK

Раздельные (java 8)

Только JDK (JRE "упакован" внутрь) (java 11)

💡 Ключевое для памяти: структура heap и metaspace осталась той же, но управление — точнее, а инструменты — мощнее.