Найти тему

Java GC: Mark-Sweep-Compact и поколения

Оглавление

Коротко о Mark-Sweep

Чтобы чистить ненужные объекты GC делает так:
1. Обходит граф всех объектов в хипе (heap) и помечает их
2. Не помеченные объекты никто не использует - их можно удалить.

Пример кода

Рассмотрим такой код:

Java-код, который плодит миллионы ненужных телефонов
Java-код, который плодит миллионы ненужных телефонов

Так будет выглядеть дерево зависимостей для каждого телефона Nokia.

Дерево зависимостей объектов
Дерево зависимостей объектов

Коротко о поколениях

Чтобы каждый раз не обходить полностью весь граф объектов, GC разделяет объекты на поколения:

  • Young generation (созданы недавно; возможно, скоро можно будет удалить)
  • Old generation (созданы давно; скорее всего, проживут еще долго)

Объекты из Old generation хранятся в Old регионе GC.

Объекты из Young generation хранятся в регионах Eden или Survivor.

Что будет происходить с мусором в примере?

Все новые объекты буду создаваться в регионе Eden. Спустя 1 000 000 итераций картина будет выглядеть так:

Заполнили Eden новыми объектами
Заполнили Eden новыми объектами

JVM замечает, что занята значительная доля памяти, и начинает Young GC (GC в молодом поколении)

Young GC

1. Нашли GC roots (безусловно достижимые объекты).

В нашем случае только те, что лежат на стеке. То есть, 1 Nokia, 1 Siemens и 1 iPhone.

Нашли GC roots
Нашли GC roots

2. Итеративно помечаем их и всех, от кого они зависят

Пометили все достижимые объекты в хипе
Пометили все достижимые объекты в хипе

3. Помеченных перекладываем в регион Survivor (выжившие). Остальных можно уничтожить (освободить память).

Переложили выживших в Survivor
Переложили выживших в Survivor

4. Если объект переживает несколько Young GC в Survivor, то он переносится в регион Old, чтобы не тратить на него время при следующих Young GC

Тех, кто долго выживал, переложили в Old
Тех, кто долго выживал, переложили в Old

Этот алгоритм GC называется Mark-Sweep-Compact (пометили используемые, смели остальные, сжали тех, кто долго выживал)

Идет время.

Прошло еще 10 000 таких Young GC. Регион Old медленно растет.

Регион Old близок к заполнению
Регион Old близок к заполнению

JVM понимает, что пора с ней что-то делать. И запускает Old GC.

Old GC

Теперь просматриваются не только объекты из Eden и Survivor, но еще и из Old. Такой GC обычно отнимает значительно больше времени и сил у CPU.

Mark фаза в Old GC
Mark фаза в Old GC
После Old GC
После Old GC

И все повторяется заново пока приложение не закончит работу или упадет с OutOfMemoryError.

Классный доклад про G1 c Java One

Про GC есть много крутых материалов. Мне больше всего помог разобраться этот доклад с Java One.

В реальной жизни

Реальные GC работают по похожему принципу, но используют много оптимизаций для улучшения производительности, например:

1. Регионов Eden, Survivor и Old может быть больше, чем по одному
2. Некоторые фазы GC (например, Mark) могут происходить без остановки приложения
3. Фазы, которые требуют остановку приложения, могут выполнятся параллельно в несколько потоков
4. И еще много ужасающе крутых оптимизаций

P.S.

Как всегда, буду рад любым вопросам и уточнениям в комментариях ;)