Найти в Дзене
Java

Устройство памяти в Java (JMM)

Основные области памяти JVM 1) Heap (Куча) Heap — это область памяти, где размещаются все объекты и их данные (поля), созданные с помощью ключевого слова new. Сама куча управляется уже нам известным сборщиком мусора (GC). Пример: public class Main { public static void main(String[] args) { String name = new String("Hello, World!"); // Объект в Heap } } 2) Stack (Стек) Стек — область памяти, выделенная для каждого потока, где хранятся: локальные переменные метода, вызовы методов (кадры стека), примитивные типы данных и ссылки на объекты в Heap. Пример: public class Main { public static void main(String[] args) { int x = 42; // Хранится в стеке } } 3) Metaspace (Метапространство) На самом деле не сильно нужное область памяти для изучения, но просто знать о ее существовании не будет лишним (чтобы на собесах покозырять) :) Metaspace — область памяти, где JVM хранит метаинформацию о классах (метаданные классов, методы, пол

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

1) Heap (Куча)

Heap — это область памяти, где размещаются все объекты и их данные (поля), созданные с помощью ключевого слова new. Сама куча управляется уже нам известным сборщиком мусора (GC).

Пример:

public class Main {

public static void main(String[] args) {

String name = new String("Hello, World!"); // Объект в Heap

}

}

2) Stack (Стек)

Стек — область памяти, выделенная для каждого потока, где хранятся: локальные переменные метода, вызовы методов (кадры стека), примитивные типы данных и ссылки на объекты в Heap.

Пример:

public class Main {

public static void main(String[] args) {

int x = 42; // Хранится в стеке

}

}

3) Metaspace (Метапространство)

На самом деле не сильно нужное область памяти для изучения, но просто знать о ее существовании не будет лишним (чтобы на собесах покозырять) :)

Metaspace — область памяти, где JVM хранит метаинформацию о классах (метаданные классов, методы, поля и константы).

Начиная с Java 8, Metaspace заменил Permanent Generation (PermGen).

Управляется автоматически и может быть ограничен через флаги JVM.

А также есть еще 4 и 5: Program Counter Register (PC), Native Method Stack. Но тут уж совсем не неужно особо рассказывать и погружаться. Достаточно знать, что счетчик регистров отвечает за хранение адресса выполняемой инструкции, а Native Method Stack отвечает за вызов нативных функций написанных на C/C++.

На рисунке ниже показано абстрактное строение памяти в Java.

Однако, в отличие от стека, работа с кучей куда сложнее. Если стек — это область памяти фиксированного размера, которая самоотчищается при завершении работы метода, то куча представляет собой динамическую область, управление которой требует более тонкой настройки. Именно здесь кроется одна из главных проблем — утечка памяти (OutOfMemoryError).

Почему это важно?
Несмотря на то, что Java берет на себя управление памятью, нам все равно нужно управлять размером кучи и ее отдельных частей в зависимости от потребностей нашего приложения. Давайте разбираться, как же устроена куча...

-2

Молодое поколение (Young Gen) — это место, где создаются все новые объекты. Когда молодое поколение заполнено, выполняется сбор мусора. Этот сбор мусора называется Minor GC . Молодое поколение разделено на три части — Eden Memory и два пространства Survivor Memory (S0 и S1) . Важные моменты о пространствах молодого поколения:

1) Большинство вновь созданных объектов находятся в пространстве памяти Эдема.

2) Когда пространство Эдема заполнено объектами, выполняется Minor GC, и все выжившие объекты перемещаются в одно из выживших пространств.

3) Minor GC также проверяет объекты survivor и перемещает их в другое пространство survivor. Таким образом, в каждый момент времени одно из пространств survivor всегда пустует.

4) Объекты, которые выжили после многих циклов GC, перемещаются в пространство памяти старого поколения. Обычно это делается путем установки порогового значения для возраста объектов молодого поколения, прежде чем они станут пригодными для продвижения в старое поколение.

На схеме выше также указаны какие-то флаги jvm без конкретных значений, почему? Потому, что выставление данных параметров требует анализа потребительности вашего приложения (Java VisualVM, JConsole). Давайте разберем эти параметры.

1) -Xms: Для установки начального размера кучи при запуске JVM

2) -Xmx: Для установки максимального размера кучи

3) -Xmn: Для установки размера Молодого поколения, остальное пространство отводится Старому поколению

4) -XX:SurvivorRatio: Для предоставления соотношения пространства Эдема и пространства Выживших, например, если размер Молодого поколения составляет 10 м, а переключатель VM -XX:SurvivorRatio=2, то 5 м будет зарезервировано для пространства Эдема и по 2,5 м для обоих пространств Выживших. Значение по умолчанию - 8

5) -XX:+PrintGCDetails - выводит информацию о сборке мусора

6) -Xlog:gc - логирование работы GC

Я выделил основные парметры, на практике их куда больше, но тут перечислять все было бы бессмысленно. Однако, есть еще одна важная настройка - это сам сборщик мусора. Вы же не думали, что он всего один :)

Разновидности сборщиков мусора (Garbage Collectors)

JVM поддерживает несколько типов сборщиков мусора (GC), которые подходят для разных сценариев и нагрузок. Разберем основные из них и как их настроить.

1) Serial GC - Однопоточный сборщик мусора, подходящий для небольших приложений или систем с ограниченными ресурсами.

-XX:+UseSerialGC

2) Parallel GC (Throughput Collector) - Многопоточный сборщик мусора, оптимизированный для максимальной пропускной способности. Подходит для серверных приложений с многопоточными нагрузками.

-XX:+UseParallelGC

3) CMS (Concurrent Mark-Sweep) GC - Сборщик с минимальными паузами, подходит для приложений, чувствительных к задержкам. Устарел с 9 Java.

-XX:+UseConcMarkSweepGC

4) G1 GC (Garbage First) - Современный сборщик, который делит кучу на регионы и работает с приоритетом на области с наибольшим количеством мусора. Ставится по умолчанию с Java 9.

-XX:+UseG1GC

5)  ZGC (Z Garbage Collector) - Сборщик с очень низкими паузами, подходящий для приложений с большими объемами памяти. Поддерживается с 11 Java.

-XX:+UseZGC

6) Shenandoah GC - Сборщик с низкими задержками, который уменьшает время пауз для больших куч. Поддерживается начиная с Java 12.

-XX:+UseShenandoahGC

И что, вы думали на этом все? Разве вам неинтересно как GC понимает, что объект надо удалить? Я все равно расскажу независимо от вашего ответа :)

Основные принципы определения удаляемых объектов

Сборщик мусора (Garbage Collector, GC) в Java использует концепцию ссылочной доступности (reachability) для определения, какие объекты можно удалить. Он отслеживает объекты, которые больше не используются, и освобождает память, занимаемую этими объектами. Давайте разберем это подробнее.

1) Корневые объекты (GC Roots)

GC начинает свою работу с анализа так называемых корневых объектов (GC Roots), которые всегда считаются достижимыми. Типичные примеры GC Roots: Ссылки на объекты из стека потоков (локальные переменные и вызовы методов).Статические переменные классов.Ссылки из JNI (Java Native Interface) и нативного кода.Ссылки из глобальных областей памяти JVM.

2) Алгоритм "обхода графа"

JVM строит граф объектов в памяти. GC запускает обход из GC Roots и помечает все объекты, которые достижимы. Объекты, до которых невозможно добраться (нет пути от GC Roots), считаются сиротами (garbage) и могут быть удалены.

Классы достижимости объектов

JVM разделяет объекты на несколько категорий в зависимости от их "достижимости". Вот основные из них:

1) Strong Reachability (Сильная достижимость)

Объект считается достижимым, если на него существует прямая сильная ссылка (обычная ссылка в Java).

Пример:

Object obj = new Object(); // obj имеет сильную ссылку на объект

2) Soft Reachability (Мягкая достижимость)

Если на объект есть только мягкие ссылки (soft references), он может быть удалён только при нехватке памяти. Используется для реализации кэшей.

Пример:

SoftReference<Object> softRef = new SoftReference<>(new Object());

3) Weak Reachability (Слабая достижимость)

Объекты с единственными слабыми ссылками (weak references) удаляются при следующем проходе GC. Используется, например, в WeakHashMap.

Пример:

WeakReference<Object> weakRef = new WeakReference<>(new Object());

4) Phantom Reachability (Фантомная достижимость)

Объекты с фантомными ссылками (phantom references) удаляются, но их можно отслеживать через ReferenceQueue для выполнения финализации.

Пример:

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue);

5) Unreachable (Недостижимость)

Если на объект больше нет ссылок или пути к нему от GC Roots, он считается недостижимым и может быть удалён.

Можно еще далеко и глубоко уходить в понятие устройства JMM, особенно в многопоточности. Можете почитать про принцип happens-before, но я все равно расскажу о нем позже.

Оставляю ссылки на различные статью на эту тему:

1) https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java

2) https://riteshpanigrahi.com/java-memory-model-and-happens-before-principle