Каждое Java приложение, после запуска, создаёт десятки, сотни, тысячи объектов в памяти компьютера на котором оно запущено. Память, при этом, ресурс не бесконечный, и поэтому необходимо использовать его эффективно. Виртуальная Машина Java (Java Virtual Machine, далее JVM) умеет грамотно распоряжаться памятью и помогает нам, разработчикам, управляя ею автоматически.

О том, как именно JVM работает с памятью во время работы Java приложения мы поговорим в этой статье.

Зачем вообще разработчику знать о памяти Java процесса?

Java - это язык программирования с автоматическим управлением памятью.

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

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

Память Java процесса

Память, выделяемая Java процессу, представляет из себя набор из двух областей:

  • PermGen (до Java 8) / Metaspace (заменил PermGen, начиная с Java 8)

  • Heap или Куча

Рисунок 1. Базовые сегменты памяти JVM
Рисунок 1. Базовые сегменты памяти JVM

Каждая из областей имеет собственное предназначение.

Metaspace

Metaspace - это область памяти в которой хранится статическая инфорация Java приложения, такая как метаданные загруженных классов. По умолчанию, metaspace увеличивается автоматически и не имеет явного ограничения. Без установленного ограничения размер metaspace неявно ограничен объёмом системной памяти хоста.

Управление Metaspace

Управлять metaspace областью можно с помощью следующих флагов JVM:

  • -XX:MetaspaceSize - минимальный объём памяти для области

  • -XX:MaxMetaspaceSize - максимальный объём памяти для области

  • -XX:MinMetaspaceFreeRatio - минимально зарезервированный размер памяти после очистки GC (в процентах)

  • -XX:MaxMetaspaceFreeRatio - максимально зарезервированный размер памяти после очистки GC (в процентах)

Heap

Heap - это область памяти в которой хранятся инстансы объектов. Каждый раз, когда разработчик создаёт инстанс какого-либо класса с помощью операции new(пример: new Object()), память под объект выделяется именно в heap'е.

Строковый пул, так же, начиная с Java 7 располагается в heap'е.

Heap, в свою очередь, содержит несколько подобластей, каждая из которых выполняет свою определённую роль. Поговорим о них подробнее. Следующие подобласти относятся к heap'у:

  • Eden

  • Survival (S0 & S1)

  • Old Gen

Рисунок 2. Сегменты Heap'а
Рисунок 2. Сегменты Heap'а

Eden

Это сегмент heap области в который свежесозданные объекты попадают в первую очередь. Каждый раз, когда в Java приложении выполняется инструкция new, память, выделяемая под новый инстанс, выделяется именно в Eden сегменте.

Для этого правила есть исключения - если размер памяти, необходимый для хранения инстанса достаточно большой, то JVM может выделить память под него сразу в Old Gen сегменте.

Надолго свежесозданные объекты в Eden сегменте не задержатся. После первого же запуска процесса сборки мусора, они либо будут удалены из памяти, либо будут перенесены в Survival сегменты heap'а.

S0 и S1 - Survival

Survival сегмент области heap'а используется JVM для хранения объектов, которые пережили один и более проходов сборщика мусора.

Survival сегмент представлен в JVM двумя сегментами - S0 и S1. Они служат неким "перевалочным пунктом" для объектов на пути к Old Gen сегменту. В S0 и S1 сегментах объекты могут провести какое-то время до тех пор, пока они не будут удалены из памяти или переведены в Old Gen сегмент.

Если быть точным, то в JVM есть настройка, позволяющая указать количество запусков сборки мусора, которое объект должен пережить, для того, чтобы попасть в Old Gen сегмент. По умолчанию, это количество равно 15.

Почему Survival область представлена двумя сегментами S0 и S1? Всё дело в том, что для ускорения очистки памяти и исправления её фрагментации, в ходе процесса сборки мусора два этих сегмента дефрагминтируются и меняются местами.

Old Gen

Old Gen сегмент heap'а используется для хранения объектов, которые пережили установленное количество запусков сборки мусора.

Полная схема памяти Java процесса выглядит следующим образом:

Рисунок 3. Полная схема памяти Java процесса
Рисунок 3. Полная схема памяти Java процесса

Управление Heap

Управлять heap областью можно с помощью следующих флагов JVM:

  • -Xms - минимальный объём памяти всей области

  • -Xmx - максимальный объём памяти всей области

  • -XX:NewSize - минимальный объём памяти Eden сегмента

  • -XX:MaxNewSize - максималный объём памяти Eden сегмента

  • -XX:SurvivorRatio - соотношение между объёмами памяти Eden и Survival сегментов


Зачем использовать разные области и сегменты памяти? Потому что это позволяет организовать процесс сборки мусора наиболее оптимальным образом для каждого из сегментов, с учётом специфики каждого.

Где это может пригодиться?

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

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

Рисунок 4. Инструмент VisualVM
Рисунок 4. Инструмент VisualVM

Заключение

В этой статье мы рассмотрели как выглядит память Java процесса, какие стадии проходит Java объект за время своей жизни. Так же мы узнали о флагах, которые позволяют контролировать работу JVM с памятью.

Работа JVM с памятью непосредственно связана с такой сложной темой как сборка мусора. И сегодня Вы сделали первый шаг на пути к пониманию потаённой стороны Java.

Список материалов

Дополнительные источники информации о коммуникациях, могут быть найдены в следующих источниках:

Ещё материалы по теме

Комментарии (2)


  1. Evigilans
    29.06.2023 15:15

    Неплохая статья, написанная простым языком.
    Но раз это введение, то значит планируется продолжение? Если да, то хотелось бы каких-нибудь примеров утечек памяти из реальной практики. Или как выбрать правильный GC в зависимости от того, какие у меня объекты в программе (например если приложение многопоточное, или очень много мелких маложивущих объектов, или, наоборот, объектов мало, но почти все они тяжёлые синглтоны).


  1. IvanVakhrushev
    29.06.2023 15:15

    -XX:MetaspaceSize - минимальный объём памяти для области

    Это не так. MetaspaceSize - это порог (a high-water mark), при достижении которого инициируется сборка мусора.