Как устроена память в Java?

Память в Java устроена таким образом, чтобы обеспечить безопасность, автоматическое управление памятью и изоляцию приложений. Основу составляет Java Memory Model (JMM), а практическая реализация памяти — это структура, которую создаёт Java Virtual Machine (JVM) во время выполнения программы.

Ниже — подробный обзор устройства памяти в Java: её областей, назначения каждой из них, а также механизмов, которые управляют памятью.

Основные области памяти в Java

1. Heap (Куча)

Это основная область памяти, где хранятся объекты и связанные с ними данные во время выполнения программы.

  • Выделяется при запуске JVM (-Xms, -Xmx)

  • Управляется сборщиком мусора (Garbage Collector)

  • Разделена на поколения:

    • Young Generation (Eden + Survivor Spaces): сюда попадают новые объекты. Часто очищается.

    • Old Generation (Tenured): объекты, пережившие несколько сборок, перемещаются сюда. Чистится реже, но дольше.

    • Metaspace (с Java 8): хранит метаинформацию классов (ранее это была PermGen до Java 7)

2. Stack (Стек)

Каждый поток получает свой собственный стек.

  • Используется для хранения:

    • Примитивных типов (int, float и т.д.)

    • Ссылок на объекты (сами объекты лежат в куче)

    • Локальных переменных

    • Адресов возврата (при вызовах методов)

  • Жёстко управляется: переменные уничтожаются после выхода из метода

  • Быстрее доступа, чем куча

Каждое новое выполнение метода порождает stack frame (кадр стека), который исчезает после завершения метода.

3. Метаспейс (Metaspace)

С Java 8 заменила область PermGen.

  • Хранит:

    • Информацию о загруженных классах

    • Метаданные классов, методы, сигнатуры и т.п.

  • Не ограничена размером по умолчанию (использует память вне кучи — native memory)

  • Управляется JVM и может быть ограничена параметром -XX:MaxMetaspaceSize

4. Code Cache (Native memory)

  • Хранит скомпилированный JIT-код

  • Участвует в оптимизации исполнения

5. Direct memory (off-heap)

  • Выделяется вне управляемой кучей

  • Используется, например, в java.nio.ByteBuffer.allocateDirect() — для работы с файлами или сетью, где важно избежать копирования из хипа

  • Управляется вручную или нативными библиотеками (например, Netty)

Управление памятью: сборка мусора (Garbage Collection)

Объекты, на которые больше не ссылается ни один активный код, считаются недостижимыми и могут быть удалены сборщиком мусора.

Существует несколько алгоритмов GC:

  • Serial GC (один поток, подходит для приложений с небольшими кучами)

  • Parallel GC (многопоточная обработка для Young/Old Generations)

  • CMS (Concurrent Mark Sweep) — устарел, но долго использовался для снижения пауз

  • G1 (Garbage First) — баланс между паузами и производительностью

  • ZGC / Shenandoah — низкие паузы, масштабируемость, подходит для больших куч (Java 11+)

Выбор GC может быть настроен с помощью параметров JVM:

\-XX:+UseG1GC
\-XX:+UseZGC

Работа памяти в многопоточности: Java Memory Model (JMM)

JMM определяет, как и когда изменения, сделанные одним потоком в переменных, становятся видимыми другим потокам.

Ключевые понятия:

  • Happens-before — гарантии порядка исполнения операций между потоками

  • Volatile — переменная всегда читается/пишется напрямую из памяти

  • Synchronized — гарантирует взаимное исключение и порядок доступа

Примитивы vs объекты

  • Примитивы (int, boolean, double...) хранятся в стеке, если это локальные переменные, или в куче, если являются полями объекта.

  • Объекты всегда хранятся в куче, а переменные ссылаются на них.

Пример

public class Example {
public static void main(String\[\] args) {
int x = 5; // стек
String s = "hello"; // ссылка в стеке  объект в куче
Person p = new Person("Alice"); // p в стеке  объект Person в куче
}
}

Диагностика и мониторинг памяти

Инструменты:

  • jvisualvm, jconsole — визуализация памяти

  • jmap, jstat — командная строка

  • GC logs — -verbose:gc, -XX:+PrintGCDetails

  • Flight Recorder, **Mission Control
    **

Таким образом, память в Java разделена на несколько логических и физических областей, каждая из которых обслуживает конкретные задачи: объекты, стек вызовов, метаданные классов, компилированный код и даже off-heap память. JVM автоматически управляет большинством этих областей, что упрощает разработку, но требует понимания устройства памяти для отладки, масштабирования и оптимизации производительности.