Сборка мусора в Java
В этой статье поговорим о сборке мусора (Garbage Collection) в java.
Память в JVM делится на три части:
- Young generation (молодое поколение).
- Old generation (старое поколение).
- Metaspace (Perm Gen).
Young Generation (молодое поколение)
Как следует из названия, Young Generation — это область памяти для новых, вновь создаваемых объектов.
- Когда область Young Generation заполняется, то запускается минорная сборка мусора (Minor GC).
- При Minor GC «мертвые» объекты удаляются из Young Generation.
- Чем больше «мертвых» объектов в Young Generation, тем быстрее выполняется Minor GC.
- При Minor GC происходит «остановка мира» (stop the world) — все потоки в приложении останавливаются.
Давайте подробнее разберемся со структурой Young generation.
Young generation разделен на три части: Eden, S0, S1.
- Все новые объекты размещаются в Eden Space.
- При заполнении Еden space происходит minor GC: все «живые» объекты перемещаются в одно из пространств уцелевших объектов (survivor space): S0 или S1. Допустим, в нашем случае, все объекты будут перемещены в S0.
Для дальнейшего эксперимента я написал и запустил программу, которая создает короткоживущие объекты.
Давайте посмотрим распределение памяти в Visual GC (плагин для VisualVM).
Как видно, в S0 совсем мало объектов, и как только Еden Space заполняется, все объекты, на которые есть ссылки, перемещаются в S1.
Old generation (старое поколение)
- В Old generation располагаются долгоживущие объекты.
- Как правило, эта область больше, чем область для young generation.
- При заполнении (или достижении заданного порога) происходит Major GC.
- Обычно Major GC выполняются медленнее и реже, чем minor GC.
Как с помощью этой информации можно оптимизировать использование памяти?
Все зависит от типа приложения.
Если у вас много временных объектов, то будет много Minor GC. В этом случае можно использовать параметр XX:NewRatio=1, чтобы распределить 50% на young generation и 50% на old generation.
По умолчанию NewRatio=2, т.е. young generation составляет 1/3 всей кучи (heap).
При множестве долгоживущих объектов можно сделать область старого поколения больше, увеличив NewRatio.
Зачем используется два survivor space?
Вы наверняка задумались над тем, почему используется два survivor space? Для борьбы с фрагментацией памяти. При каждом копировании объектов из eden в survivor, вы получаете пустой eden и один пустой survivor.
Алгоритмы сборки мусора
В JVM есть несколько сборщиков мусора с разными алгоритмами для молодого и старого поколения. Есть три типа алгоритмов.
Serial collector (последовательный сборщик)
Для сборки мусора используется один поток. Подходит для простых приложений с однопроцессорными машинами.
Parallel collector (параллельный сборщик)
В отличие от последовательного сборщика, использующего один поток, параллельный использует несколько потоков. Эффективен при достаточном объеме памяти и количестве процессорных ядер.
Concurrent collector (конкурентный сборщик)
Concurrent collector выполняет сборку мусора вместе с работой потоков вашего приложения. Эффективен для приложений, обрабатывающих средние и большие наборы данных и требующих малого времени отклика.
Для молодого и старого поколений можно использовать разные, но совместимые, алгоритмы GC. Например, нельзя использовать Parallel Scavenge для молодого поколения одновременно с Concurrent Mark Sweep для старого, так как Parallel Scavenge не обеспечивает синхронизацию, которая требуется в CMS.
После Java 8 в сборке мусора произошло много изменений, о которых я расскажу в других статьях.
Приглашаем всех желающих на demo-урок «Переопределение, скрытие, передекларация». На занятии рассмотрим переопределение и скрытие методов в Java, а также передекларация и скрытие переменных; познакомимся с четырьмя правилами, а потом ещё и с пятым. Регистрация доступна по ссылке.