Что такое сборщик мусора в java
Перейти к содержимому

Что такое сборщик мусора в java

  • автор:

Как работает сборка мусора?

Очередной вопрос, ответ на который нужно начинать с уточнения: в каком именно сборщике мусора? Понятие сборщика мусора вводится в спецификации JVM, но внутренности зависят от реализации. Одна JVM может содержать несколько сборщиков, один сборщик может применять разные алгоритмы в разных случаях. Вообще говоря, в теории GC может делать ничего. Метод System.gc() обещает, что сборщик сделает «лучшую попытку» освободить память, то есть по факту не дает никаких гарантий.

GC (garbage collector) – центральная тема шуток про «джава тормозит». Это необходимая плата за стабильное автоматическое управление памятью. Поэтому это одна из самых бурлящих и меняющихся областей мира Java.

Основные подходы к сборке мусора – подсчет ссылок (reference counting) и обход графа достижимых объектов (mark-and-sweep, copying collection). Первый подход испытывает трудности с циклическими ссылками, в Java в основном используется второй.

Большинство сборщиков опирается на слабую гипотезу о поколениях. Гипотеза предполагает, что молодые объекты умирают чаще. Для этого куча делится на регионы по времени жизни объектов – поколения. Сборка мусора в них выполняется раздельно.

Общий для большинства сборщиков алгоритм описан во множестве статей, например, в этой. Суть его в том, что достижимые объекты помечаются и группируются, а недостижимые удаляются.

GC Roots – то, с чего начинается обход графа объектов на вопрос достижимости. Множество корневых объектов (root set) считается достижимым безусловно. Часто на интервью просят их перечислить.

Важное понятие для сборщиков мусора – Stop The World пауза. Это полная остановка потоков программы для безопасной сборки мусора и других системных операций. Происходит в специальных местах программы, которые называются safepoint.

Конкретный сборщик в HotSpot указывается в параметре запуска JVM. Каждый сборщик имеет много специфичных для него настроек. В Java 10 HotSpot доступно 4 сборщика:

�� Serial – однопоточный, с поколениями. Дает большой throughput (маленькая сумма задержек);
�� Parallel – многопоточный вариант Serial;
�� CMS (Concurrent Mark-Sweep) – дает меньшую latency (маленькие отдельные паузы), выполняя часть сборки вне Stop The World. Плата за это – меньший throughput. Способ сборки примерно как в предыдущих, работает с поколениями. В Java 9 уже объявлен deprecated;
�� G1 (Garbage First) – тоже направлен на уменьшение latency. Вместо поколений оперирует регионами;
�� Скоро будет добавлен новый сборщик Shenandoah;

Настоятельно рекомендуется к изучению очередной доклад Шипилёва (с продолжением) и цикл статей на хабре.

Сборщик мусора Garbage Collection

Чтобы понять, как работает сборщик мусора Garbage Collection, необходимо иметь представление о распределении памяти в JVM (Java Virtual Machine). Данная статья не претендует на то, чтобы покрыть весь объем знаний о распределении памяти в JVM и описании Garbage Collection, поскольку он слишком огромен. Да, к тому же, об этом достаточно информации уже имеется в Сети, чтобы желающие могли докапаться до ее глубин. Но, думаю, данной статьи будет достаточно, чтобы иметь представление о том, как JVM работает с памятью java-приложения.

Респределение памяти в JVM

Для рассмотрения вопроса распределения памяти JVM будем использовать широко распространенную виртуальную машину для Windows от Oracle HotSpot JVM (раньше был от Sun). Другие виртуальные машины (из комплекта WebLogic или open source JVM из Linux) работают с памятью по похожей на HotSpot схеме. Возможности адресации памяти, предоставляемые архитектурой ОС, зависят от разрядности процессора, определяющего общий диапазон емкости памяти. Так, например, 32-х разрядный процессор обеспечивает диапазон адресации 2 32 , то есть 4 ГБ. Диапазон адресации для 64-разрядного процессора (2 64 ) составляет 16 экзабайт.

Разделение памяти JVM

Память процесса делится на Stack (стек) и Heap (куча) и включает 5 областей :

  • Stack
    • Permanent Generation — используемая JVM память для хранения метаинформации; классы, методы и т.п.
    • Code Cache — используемая JVM память при включенной JIT-компиляции; в этой области памяти кешируется скомпилированный платформенно-зависимый код.
    • Eden Space — в этой области выделяется память под все создаваемые программой объекты. Жизненный цикл большей части объектов, к которым относятся итераторы, объекты внутри методов и т.п., недолгий.
    • Survivor Space — здесь хранятся перемещенные из Eden Space объекты после первой сборки мусора. Объекты, пережившие несколько сборок мусора, перемещаются в следующую сборку Tenured Generation.
    • Tenured Generation хранит долгоживущие объекты. Когда данная область памяти заполняется, выполняется полная сборка мусора (full, major collection).
    Permanent Generation

    Область памяти Permanent Generation используется виртуальной машиной JVM для хранения необходимых для управления программой данных, в том числе метаданные о созданных объектах. При каждом создании объекта JVM будет сохранять некоторый набор данных об объекте в области Permanent Generation. Соответственно, чем больше создается в программе объектов, тем больше требуется «пространства» в Permanent Generation.

    Размер Permanent Generation можно задать двумя параметрами виртуальной машины JVM :

    • -XX:PermSize – минимальный размер выделяемой памяти для Permanent Generation;
    • -XX:MaxPermSize – максимальный размер выделяемой памяти для Permanent Generation.

    Для «больших» Java-приложений можно при запуске определить одинаковые значения данных параметров, чтобы Permanent Generation была создана с максимальным размером. Это может увеличить производительность, поскольку динамическое изменение размера Permanent Generation является «дорогостоящей» (трудоёмкой) операцией. Определение одинаковых значений этих параметров может избавить JVM от выполнения дополнительных операций, связанных с проверкой необходимости изменения размера Permanent Generation.

    Область памяти Heap

    Куча Heap является основным сегментом памяти, где хранятся создаваемые объекты. Heap делится на два подсегмента : Tenured (Old) Generation и New Generation. New Generation в свою очередь делится на Eden Space и Survivor.

    При создании нового объекта, когда используется оператор ‘new’, например byte[] data = new byte[1024], этот объект создаётся в сегменте Eden Space. Кроме, собственно данных для массива байт, создается также ссылка (указатель) на эти данные. Если места в сегменте Eden Space уже нет, то JVM выполняет сборку мусора. При сборке мусора объекты, на которые имеются ссылки, не удаляются, а перемещаются из одной области в другую. Так, объекты со ссылками перемещаются из Eden Space в Survivor Space, а объекты без ссылок удаляются.

    Если количество используемой Eden Space памяти превышает некоторый заданный объем, то Garbage Collection может выполнить быструю (minor collection) сборку мусора. По сравнению с полной сборкой мусора данный процесс занимает немного времени, и затрагивает только область Eden Space — устаревшие объекты без ссылок удаляются, а выжившие перемещаются в область Survivor Space.

    Размер сегмента Heap можно определить двумя параметрами : Xms (минимум) и -Xmx (максимум).

    В чем отличие между сегментами Stack и Heap?
    • Heap (куча) используется всеми частями приложения, а Stack используется только одним потоком исполнения программы.
    • Новый объект создается в Heap, а в памяти Stack’a размещается ссылка на него. В памяти стека также размещаются локальные переменные примитивных типов.
    • Объекты в куче доступны из любого места программы, в то время, как стековая память не доступна для других потоков.
    • Если память стека полностью занята, то Java Runtime вызывает исключение java.lang.StackOverflowError, а если память кучи заполнена, то вызывается исключение java.lang.OutOfMemoryError: Java Heap Space.
    • Размер памяти стека, как правило, намного меньше памяти в куче. Из-за простоты распределения памяти (LIFO), стековая память работает намного быстрее кучи.

    Garbage Collector

    Сборщик мусора Garbage Collector выполняет всего две задачи, связанные с поиском мусора и его очисткой. Для обнаружения мусора существует два подхода :

    • Reference counting – учет ссылок;
    • Tracing – трассировка.
    Reference counting

    Суть подхода «Reference counting» связана с тем, что каждый объект имеет счетчик, который хранит информацию о количестве указывающих на него ссылок. При уничтожении ссылки счетчик уменьшается. При нулевом значении счетчика объект можно считать мусором.

    Главным недостатком данного подхода является сложность обеспечения точности счетчика и «невозможность» выявлять циклические зависимости. Так, например, два объекта могут ссылаться друг на друга, но ни на один из них нет внешней ссылки. Это сопровождается утечками памяти. В этой связи данный подход не получил распространения.

    Tracing

    Главная идея «Tracing» связана с тем, что до «живого» объекта можно добраться из корневых точек (GC Root). Всё, что доступно из «живого» объекта, также является «живым». Если представить все объекты и ссылки между ними как дерево, то необходимо пройти от корневых узлов GC Roots по всем узлам. При этом узлы, до которых нельзя добраться, являются мусором.

    Данный подход, обеспечивающий выявление циклических ссылок, используется в виртуальной машине HotSpot VM. Теперь, осталось понять, а что представляет из себя корневая точка (GC Root)? «Источники» говорят, что существуют следующие типы корневых точек :

    • Основной Java поток.
    • Локальные переменные в основном методе.
    • Статические переменные основного класса.

    Таким образом, простое java-приложение будет иметь следующие корневые точки:

    • Параметры main метода и локальные переменные внутри main метода.
    • Поток, который выполняет main.
    • Статические переменные основного класса, внутри которого находится main метод.

    Очистка памяти

    Имеется несколько подходов к очистке памяти, которые в совокупности определяют принцип функционирования Garbage Collection.

    Copying collectors

    При использовании «Copying collectors» область памяти делится на две части : в одной части размещаются объекты, а вторая часть остается чистой. На время очистки мусора приложение останавливает работу и запускается сборщик мусора, который находит в первой области объекты со ссылками и переносит их во вторую (чистую) область. После этого, первая область очищается от оставшихся там объектов без ссылок, и области меняются местами.

    Главным достоинством данного подхода является плотное заполнение памяти. Недостатком «Copying collectors» является необходимость остановки приложения и размеры двух частей памяти должны быть одинаковыми на случай, когда все объекты остаются «живыми».

    Данный подход в чистом виде в HotSpot VM не используется.

    Mark-and-sweep

    При использовании «mark-and-sweep» все объекты размещаются в одном сегменте памяти. Сборка мусора также приостанавливает приложение, и Garbage Collection проходит по дереву объектов, помечая занятые ими области памяти, как «живые». После этого, все не помеченные участки памяти сохраняются в «free list», в которой будут, после завершения сборки мусора, размещаться новые объекты.

    К недостаткам данного подхода следует отнести необходимость приостановки приложения. Кроме этого, время сборки мусора, как и время приостановки приложения, зависит от размера памяти. Память становится «решетчатой», и, если не применить «уплотнение», то память будет использоваться неэффективно.

    Данный подход также в чистом виде в HotSpot VM не используется.

    Generational Garbage Collection

    JVM HotSpot использует алгоритм сборки мусора типа «Generational Garbage Collection», который позволяет применять разные модули для разных этапов сборки мусора. Всего в HotSpot реализовано четыре сборщика мусора :

    • Serial Garbage Collection
    • Parallel Garbage Collection
    • CMS Garbage Collection
    • G1 Garbage Collection

    Serial Garbage Collection относится к одним из первых сборщиков мусора в HotSpot VM. Во время работы этого сборщика приложение приостанавливается и возобновляет работу только после прекращения сборки мусора. В Serial Garbage Collection область памяти делится на две части («young generation» и «old generation»), для которых выполняются два типа сборки мусора :

    • minor GC – частый и быстрый c областью памяти «young generation»;
    • mark-sweep-compact – редкий и более длительный c областью памяти «old generation».

    Область памяти «young generation», представленная на следующем рисунке, разделена на две части, одна из которых Survior также разделена на 2 части (From, To).

    Алгоритм работы minor GC

    Алгоритм работы minor GC очень похож на описанный выше «Copying collectors». Отличие связано с дополнительным использованием области памяти «Eden». Очистка мусора выполняется в несколько шагов :

    • приложение приостанавливается на начало сборки мусора;
    • «живые» объекты из Eden перемещаются в область памяти «To»;
    • «живые» объекты из «From» перемещаются в «To» или в «old generation», если они достаточно «старые»;
    • Eden и «From» очищаются от мусора;
    • «To» и «From» меняются местами;
    • приложение возобновляет работу.

    В результате сборки мусора картинка области памяти изменится и будет выглядеть следующим образом :

    Некоторые объекты, пережившие несколько сборок мусора в области From, переносятся в «old generation». Следует, также отметить, что и «большие живые» объекты могут также сразу же пеместиться из области Eden в «old generation» (на картинке не показаны).

    Алгоритм работы mark-sweep-compact

    Алгоритм «mark-sweep-compact» связяан с очисткой и уплотнением области памяти «old generation».

    Принцип работы «mark-sweep-compact» похож на описанный выше «Mark-and-sweep», но добавляется процедура «уплотнения», позволяющая более эффективно использовать память. В процедуре живые объекты перемещаются в начало. Таким образом, мусор остается в конце памяти.

    При работе с областью памяти используется механизм «bump-the-pointer», определяющий указатель на начало свободной памяти, в которой размещается создаваемый объект, после чего указатель смещается. В многопоточном приложении используется механизм TLAB (Thread-Local Allocation Buffers), который для каждого потока выделяет определенную область памяти.

    Избавляемся от мусора в Java

    Для работы любого приложения требуется память. Однако память компьютера ограничена. Поэтому важно ее очищать от старых неиспользуемых данных, чтобы освободить место для новых.

    Кто занимается этой очисткой? Как и когда очищается память? Как выглядит структура памяти? Давайте разберем с этим подробнее.

    Структура памяти Java

    Память в Java состоит из следующих областей:

    Структура памяти Java

    Native Memory — вся доступная системная память.

    Heap (куча) — часть native memory, выделенная для кучи. Здесь JVM хранит объекты. Это общее пространство для всех потоков приложения. Размер этой области памяти настраивается с помощью параметра -Xms (минимальный размер) и -Xmx (максимальный размер).

    Stack (стек) — используется для хранения локальных переменных и стека вызовов метода. Для каждого потока выделяется свой стек.

    Metaspace (метаданные) — в этой памяти хранятся метаданные классов и статические переменные. Это пространство также является общими для всех. Так как metaspace является частью native memory, то его размер зависит от платформы. Верхний предел объема памяти, используемой для metaspace, можно настроить с помощью флага MaxMetaspaceSize.

    PermGen (Permanent Generation, постоянное поколение) присутствовало до Java 7. Начиная с Java 8 ему на смену пришла область Metaspace.

    CodeCache (кэш кода) — JIT-компилятор компилирует часто исполняемый код, преобразует его в нативный машинный код и кеширует для более быстрого выполнения. Это тоже часть native memory.

    Сборка мусора: введение

    Что такое «мусор»? Мусором считается объект, который больше не может быть достигнут по ссылке из какого-либо объекта. Поскольку такие объекты больше не используются в приложении, то их можно удалить из памяти.

    Например, на диаграмме ниже объект fruit2 может быть удален из памяти, поскольку на него нет ссылок.

    Мусор

    Что такое сборка мусора? Сборка мусора — это процесс автоматического управления памятью. Освобождение памяти (путем очистки мусора) выполняется автоматически специальным компонентом JVM — сборщиком мусора (Garbage Collector, GC). Нам, как программистам, нет необходимости вмешиваться в процесс сборки мусора.

    Источник: Oracle.com

    Сборка мусора: процесс

    Для сборки мусора используется алгоритм пометок (Mark & Sweep). Этот алгоритм состоит из трех этапов:

    Mark & Sweep GC

    1. Mark (маркировка). На первом этапе GC сканирует все объекты и помечает живые (объекты, которые все еще используются). На этом шаге выполнение программы приостанавливается. Поэтому этот шаг также называется «Stop the World» .
    2. Sweep (очистка). На этом шаге освобождается память, занятая объектами, не отмеченными на предыдущем шаге.
    3. Compact (уплотнение). Объекты, пережившие очистку, перемещаются в единый непрерывный блок памяти. Это уменьшает фрагментацию кучи и позволяет проще и быстрее размещать новые объекты.

    Поколения объектов

    Что такое поколения объектов?

    Для оптимизации сборки мусора память кучи дополнительно разделена на четыре области. В эти области объекты помещаются в зависимости от их возраста (как долго они используются в приложении).

    Поколения в куче

    1. Young Generation (молодое поколение). Здесь создаются новые объекты. Область young generation разделена на три части раздела: Eden (Эдем), S0 и S1 (Survivor Space — область для выживших).
    2. Old Generation (старое поколение). Здесь хранятся давно живущие объекты.
    Что такое Stop the World?

    Когда запускается этап mark, работа приложения останавливается. После завершения mark приложение возобновляет свою работу. Любая сборка мусора — это «Stop the World».

    Что такое гипотеза о поколениях?

    Как уже упоминалось ранее, для оптимизации этапов mark и sweep используются поколения. Гипотеза о поколениях говорит о следующем:

    1. Большинство объектов живут недолго.
    2. Если объект выживает, то он, скорее всего, будет жить вечно.
    3. Этапы mark и sweep занимают меньше времени при большом количестве мусора. То есть маркировка будет происходить быстрее, если анализируемая область небольшая и в ней много мертвых объектов.

    Таким образом, алгоритм сборки мусора, использующий поколения, выглядит следующим образом:

    Сборка мусора поколениями

    1. Новые объекты создаются в области Eden. Области Survivor (S0, S1) на данный момент пустые.
    2. Когда область Eden заполняется, происходит минорная сборка мусора (Minor GC). Minor GC — это процесс, при котором операции mark и sweep выполняются для young generation (молодого поколения).
    3. После Minor GC живые объекты перемещаются в одну из областей Survivor (например, S0). Мертвые объекты полностью удаляются.
    4. По мере работы приложения пространство Eden заполняется новыми объектами. При очередном Minor GC области young generation и S0 очищаются. На этот раз выжившие объекты перемещаются в область S1, и их возраст увеличивается (отметка о том, что они пережили сборку мусора).
    5. При следующем Minor GC процесс повторяется. Однако на этот раз области Survivor меняются местами. Живые объекты перемещаются в S0 и у них увеличивается возраст. Области Eden и S1 очищаются.
    6. Объекты между областями Survivor копируются определенное количество раз (пока не переживут определенное количество Minor GC) или пока там достаточно места. Затем эти объекты копируются в область Old.
    7. Major GC. При Major GC этапы mark и sweep выполняются для Old Generation. Major GC работает медленнее по сравнению с Minor GC, поскольку старое поколение в основном состоит из живых объектов.
    Преимущества использования поколений

    Minor GC происходит в меньшей части кучи (~ 2/3 от кучи). Этап маркировки эффективен, потому что область небольшая и состоит в основном из мертвых объектов.

    Недостатки использования поколений

    В каждый момент времени одно из пространств Survivor (S0 или S1) пустое и не используется.

    Сборка мусора: флаги

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

    Что такое сборщик мусора в java

    то есть обьекты в ходе уборки мусора постепенно перемещаются и затем попадают в old, из которой их убирают только после переполнения этой области памяти.

    Anatoly Уровень 28
    28 августа 2023
    Kiriall Уровень 12
    19 июня 2023

    Очень интересно и логично) Один только момент не понял про чистку Eden. Сказано, что там 2 алгоритма работы, и ,например, если мусора меньше, то он помечается и удаляется из этой области, а оставшиеся живые объекты компонуются. А потом говорится про Survival Space «куда переносятся все объекты, пережившие хотя бы одну сборку мусора». Так получается, что всегда «живые» объекты после чистки из Eden переносятся в Survival Space независимо от алгоритма чистки Eden, или только про чистке по алгоритму «мусора больше»?

    Ислам Уровень 32
    26 мая 2023
    Очень интересная лекция
    IrinaVyu Уровень 22
    6 мая 2023
    отличная статья. у этого автора все статьи хороши,кстати говоря
    Александр Потапов Уровень 40
    17 апреля 2023

    Что будет если объект станет мусором в 3-м поколении, он все равно доживет до Old Generation? Как работает сборщик мусора в Survival Space? Хорошая статья, спасибо!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *