Память в JVM

Память в JVM

Дорогу осилит идущий

Сегодня мы познакомимся с устройством памяти в виртуальной машине Java.

Эти знания важны как для прохождения собеседований, так и для понимания ряда предстоящих и даже пройденных тем: многопоточность, работа со String, Garbage Collector (сборщик мусора), ошибки (как вы можете помнить, некоторые из них связаны с переполнением различных областей памяти).

Урок исключительно теоретический, поэтому практической части сегодня не будет.

В рамках статьи будут регулярные отсылки на настройки JVM. Не уверен, что мы затронем эту тему в рамках курса, поэтому могу порекомендовать самостоятельно с ней ознакомиться в целях расширения кругозора, она, в целом, несложная, но и в работе нужна крайне редко и далеко не всем.

В упрощенной картине мира, память в Java можно разбить на 3 блока: Permanent Generation (PermGen), Heap (куча), Stack(стек). У каждого из них своя зона ответственности и свои особенности. Рассмотрим каждый из типов подробнее.


PermGen. MetaSpace.

Зона ответственности PermGen — хранение объектов типа Class. До Java 8 он имел фиксированный размер, который можно было изменить через настройки JVM.

Основная проблема PermGen — он не расширяем динамически. Поэтому в Java 8 (и последующих версиях) он был заменен на MetaSpace. Основное отличие как раз в том, что MetaSpace расширяется автоматически, по мере необходимости. В настройках JVM можно ограничить его максимальный размер.

Очистка мусора происходит при полном заполнении (у PermGen) или перед динамическим расширением (у MetaSpace).


Heap (куча)

Самая большая область памяти в Java. Именно в ней хранятся объекты, создаваемые в программе. И именно на эту область памяти направлен, в первую очередь, сборщик мусора (GC) — его задачей является удаление объектов, на которые в программе не осталось активных ссылок. Размер хипа (сленговое название от англ. heap) также можно указать в настройках JVM.

Хип делится, в свою очередь, на несколько частей, актуальность такого разделения мы разберем в рамках знакомства с Garbage Collector.

Куча разбита на несколько разделов.

Первый из них — New (он же Young) Generation. Он делится на два пространства:

  1. Eden Space – сюда помещаются все созданные объекты. Когда eden space заполняется, GC запускает быструю сборку мусора, затрагивающую только эту область. Объекты, которые не были удалены, помещаются в Survivor Space;
  2. Survivor Space – сюда помещаются объекты из eden space, пережившие быструю сборку мусора. Survivor Space, в свою очередь, делится на S0 и S1. Подробнее об этом поговорим в теме GC.

Кроме New Generation существует, неожиданно, и Old Generation. Здесь скапливаются объекты, которые пережили несколько сборок мусора (напомню, мусор — объекты, на которые не осталось ссылок). Если Old Generation оказывается полностью заполненным, сборщик мусора начинает достаточно дорогой процесс полной сборки мусора, которая затрагивает все области кучи. Если даже после этого память в куче не освободилась — возникает знакомый нам по теме исключений OutOfMemoryError. Как правило, Old Generation занимает 2/3 памяти heap-а.

Для тех, кто знаком со структурами данных и не только: объекты в хипе хранятся в структуре «полное бинарное дерево». Эта информация не имеет практического применения, но позволяет избавиться от картинки, которую мозг рисует для слова «куча».

Кроме озвученных разделов, в heap существует отдельная область памяти для пула строк (String pool). С ним мы познакомимся подробнее в одном из ближайших уроков. Вкратце, он нужен для оптимизации работы со строками, обеспечивая переиспользуемость строковых литералов. Иными словами, именно он позволяет не создавать новый объект строки, если строка с таким значением уже существует, а использовать старый. Это возможно благодаря тому, что String — immutable.

До Java 7 пул строк находился в PermGen, что приводило к проблемам со сборкой мусора, ведь строка, в целом, менее долгоживущий объект, чем объекты Class. Поэтому в дальнейшем String Pool был перенесен в Heap.

Последней областью памяти в хипе, с которой мы познакомимся, будет Method Area. Method Area – это область кучи, которая создается при старте JVM. Она выделяется для классов, методов, интерфейсов, иными словами, хранит кодовую базу приложения в скомпилированном виде.


Stack(Стек) и то, что рядом

Прежде чем начнем знакомство со стеком, нужно отметить, что PermGen/MetaSpace и Heap — одни на приложение. А вот стеков в приложении может быть много — по одному на каждый поток (thread). При знакомстве с многопоточностью станет немного понятнее, в чем принципиальная разница.

Итак, stack — область памяти, которая создается для каждого потока, в ней хранятся значения примитивов и ссылки на объекты. Как только метод завершает работу, стек освобождается для следующего метода. Размер стека можно настроить в JVM.

Стек использует (кто бы мог подумать) структуру данных стек. Она (структура данных) работает по принципу LIFO — last in, first out (последним вошел — первым вышел). Подробнее с этой структурой данных, как и рядом других, мы познакомимся позже. Переполнение памяти в стеке приведет к ошибке StackOverflowError.

Кроме стека, есть еще 2 области памяти, которые также создаются для каждого потока. Не страшно, если вы не запомните это в полном объеме, просто держите в голове, что они есть. Сейчас нам эта информация необходима, чтобы лучше понимать, откуда берутся данные, которые загружает в себя стек.

Стек Native Method стек нативных методов — используется для методов, помеченных ключевым словом native. Мы не можем работать с ними так же, как с обычными методами, поскольку не имеем доступа к их переменным — нативные методы реализованы на языках, отличных от Java. Соответственно, необходим механизм, который будет обеспечивать их вызов и обработку. Именно для этого и нужен стек нативных методов.

Program Counter Register (pcRegister) — в один момент времени один конкретный поток может выполнять лишь одну инструкцию (метод, оператор). pcRegister хранит адрес этой инструкции в памяти. Переменные (примитивы, ссылки на объекты) этой инструкции будут храниться в стеке.

Если метод, выполняемый в данный момент времени, нативный — pcRegister будет пустым, адрес этого метода будет храниться в стеке нативных методов.


Итог

Безусловно, память JVM несколько сложнее, чем описывает текущая статья. И полноценно ее понять можно лишь рассматривая вместе с логикой, которую реализует JVM в процессе выполнения. Однако наша текущая задача — первичное знакомство, поэтому ограничимся тем, что озвучено выше.

Если есть желание разобраться подробнее — предлагаю документацию Oracle. К сожалению, не подскажу, есть ли качественный перевод на русский. Ссылка: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html


На сегодня все!


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page