Сборка мусора. Как работает? Какие сборщики знаете? Какие есть области памяти в JVM? Что будет с двумя или более объектами, которые ссылаются только друг на друга, но больше не на кого и никому не нужны - как с ними поступит сборщик и как именно это будет делать?
Память в Java делится на Stack и Heap.
Stack - это область памяти, доступ к которой организован в порядке LIFO. Сюда помещается frame - локальные переменные и параметры вызываемого метода. Здесь можно сразу уточнить, что примитивы хранятся на стеке, а вот у объектов тут хранится только ссылка, а сами объекты в Heap. НО, благодаря Escape Analysis и скаляризации из Java 6, объекты, которые являются исключительно локальными и не возвращаются за пределы выполняемого метода, также сохраняются в стеке. Про Escape Analysis и скаляризацию есть доклад (видео или текст) Руслана Черемина, или ещё тут.
Frame создаётся и кладётся на Stack при вызове метода. Frame уничтожается, когда завершается его вызов метода, как в случае нормального завершения, так и в результате выброса неперехваченного исключения. У каждого потока есть свой Stack и он имеет ограниченный размер. Подробности можно посмотреть в JVM Specification.
Теперь про Heap и сборку мусора. Тут большинство просто хочет услышать то, что написано в одном из сообщений telegram-канала Senior's Blog. Процитирую основную часть здесь:
Heap делится на два поколения:
- Young Generation
- Eden
- Survivor 0 и Survivor 1
- Old Generation
- Tenured
Young разделен на три части: Eden, Survivor 0 и Survivor 1. В Eden создаются все новые объекты. Один из Survivor регионов всегда пустой. При полном заполнении региона Eden запускается малая сборка мусора, и все живые объекты из Eden и Survivor перемещаются в пустой Survivor, а Eden и использующийся Survivor полностью очищается. Это делается для уменьшения фрагментации памяти. Объекты, которые несколько раз перемещаются между Survivor, затем помещаются в Tenured.
В случае, когда места для новых объектов не хватает уже в Tenured, в дело вступает полная сборка мусора, работающая с объектами из обоих поколений. При этом старшее поколение не делится на подрегионы по аналогии с младшим, а представляет собой один большой кусок памяти. Поэтому после удаления мертвых объектов из Tenured производится не перенос данных (переносить уже некуда), а их уплотнение, то есть размещение последовательно, без фрагментации. Такой механизм очистки называется Mark-Sweep-Compact по названию его шагов (пометить выжившие объекты, очистить память от мертвых объектов, уплотнить выжившие объекты).
Бывают еще объекты-акселераты, размер которых настолько велик, что создавать их в Eden, а потом таскать за собой по Survivor’ам слишком накладно. В этом случае они размещаются сразу в Tenured.
Младшее поколение занимает одну треть всей кучи, а старшее, соответственно, две трети. При этом каждый регион Survivor занимает одну десятую младшего поколения, то есть Eden занимает восемь десятых.
Существуют следующие реализации GC:
- Serial Garbage Collector
- Parallel Garbage Collector. По умолчанию в Java 8.
- Concurrent Mark Sweep (CMS). Deprecated с Java 9.
- Garbage-First (G1). По умолчанию с Java 9. Есть видео от Владимира Иванова. Ещё можно почитать о G1 в туториале по настройке от Oracle.
- Z Garbage Collector (ZGC)
- Shenandoah Garbage Collector. Есть в наличии с Java 12. Тут, конечно же, нужно смотреть доклады Алексея Шипилёва - раз, два
Если совсем кратко, то можно ознакомиться тут и вот тут.
Почитать на Хабре подробнее про сборку мусора в Java можно в серии статей "Дюк, вынеси мусор!" от @alygin - раз, два, три.
Послушать про работу с памятью и сборщиках мусора можно в выпуске 74 подкаста Podlodka с Алексеем Шипилёвом в гостях. Обязательно загляните в полезные ссылки к выпуску.
Ещё можно вспомнить про:
- Method Area - область памяти с информацией о классах, включая статические поля. Одна на всю JVM.
- Program Counter (PC) Register - отдельный на каждый поток регистр для хранения адреса текущей выполняемой инструкции.
- Run-time Constant Pool - выделяется из Method Area для каждого класса или интерфейса. Грубо говоря, хранит литералы. Подробнее.
- Native Method Stack - собственно Stack для работы нативных методов.
Дополнительно про gc и саму JVM (ох, бохатая и животрепещущая тема):
- На богомерзком medium в картинках
- Перевод статьи Алексея Шипилёва на Хабре - Самодельный сборщик мусора для OpenJDK
- Отрывок из Java Garbage Collection Handbook про reachability algorithm
- Статейка на Википедии про Tracing garbage collection
- Доклад Simone Bordet про ZGC и Shenandoah
- JVM Anatomy Quarks - серия постов от Алексея Шипилёва про устройство JVM. Это просто клад, за который будут воевать пришельцы на постапокалиптическую Землю, чтобы разгадать, как работает эта чёртва шайтан-виртуал-машина и промышленный код почивших человеков.