Академический Документы
Профессиональный Документы
Культура Документы
программирования
Сборка мусора
Введение
Что такое сборка мусора?
Сборка мусора — это процесс восстановления заполненной памяти
среды выполнения путем уничтожения неиспользуемых объектов.
Утечка памяти
В таких языках, как C и C++, программист отвечает как за
создание, так и за уничтожение объектов. Иногда программист
может забыть уничтожить бесполезные объекты, и выделенная им
память не освобождается. Расходуется все больше и больше
системной памяти, и в конечном итоге она больше не выделяется.
Такие приложения страдают от “утечек памяти”.
Утечка памяти
После определенного момента памяти уже не хватает для создания
новых объектов, и программа внештатно завершается из-за
OutOfMemoryErrors.
В C++ для сборки мусора можно воспользоваться оператором
delete, а в C — методом free(). В managed-языках сборка мусора
происходит автоматически в течение всего времени работы
программы. Это устраняет необходимость выделения памяти и,
следовательно, позволяет избежать утечек.
Утечка памяти
Не допускать ситуаций с утечками памяти вроде бы и не трудно — нужно
всего лишь "класть на место всё что взяли", но на практике это очень
сильно осложняется хитростью архитектуры, нелинейным порядком
выполнения операторов, например, из-за применения исключений, а
также человеческим фактором.
Человеку присуще делать ошибки, что-то забывать и пропускать. И по
отношению к памяти такие вещи крайне опасны, губительны, а иногда и
непростительны.
При этом не менее важна не только утечка памяти, но и доступ уже к
высвобожденным частям. Нередки ситуации, когда память
освобождена, а указатель на нее остался и разработчик по этому
указателю обращается, в результате чего случаются ужасные вещи
Например ошибка по типу Segmentation fault.
Утечка памяти
Учитывая еще то, что вы должны думать о бизнес логике в
приложении получается двойная нагрузка на разработчика.
Разумеется, логичным желанием было бы делегировать эту
рутинную работу кому-то.
Существует даже специальные программы-анализаторы, задачей
которых является как раз поиск проблемных мест и утечек памяти,
то т.к. мы используем высокоуровневые языки с автоматическим
управлением памятью, об анализаторах используемых в C/C++
сегодня мы говорить не будем.
Автоматическое управление памятью
Как вы знаете, Java код транслируется в байткод, который уже в
свою очередь выполняется JVM.
Таким образом, JVM играет ключевую роль в работе приложения и
предоставляет разработчикам преимущества, среди которых есть
одно настолько значимое, что не поговорить об этом было бы
преступлением.
• Reference counting
• Tracing
Reference Counting
Данный подход основан на подсчете ссылок, о чем можно
догадаться из названия.
Суть подхода состоит в том, что каждый объект имеет некоторый
счетчик. Этот счетчик хранит информацию о том, сколько ссылок
указывает на объект. Когда какая-либо ссылка уничтожается, то и
значение счетчика уменьшается.
Если значение счетчика равно нулю - объект можно считать
мусором и память, которую он занимает, можно очищать.
Reference Counting
Используется во многих языках программирования, например, в Python.
У этого подхода есть много плюсов: он прост, не требует долгих пауз для
сборки мусора.
Однако, есть и несколько существенных минусов:
• Плохо сочетается с многопоточностью
• Сложно выявлять циклические зависимости, что требует большой
ответственности для реализации точности счетчика.
• Влияет на производительность – каждый раз, когда мы что-то читаем,
записываем ссылку на объект в локальную переменную, нам нужно
увеличивать счетчик.
Reference Counting
Циклические зависимости - когда два объекта указывают друг на
друга, но ни один живой объект на них не ссылается. Это приводит
к утечкам памяти.
Рассмотрим на примере…
Tracing
Tracing
Так вот Person - это и есть тот самый
корень, якорь.
Т.е это наивысшая точка графа
связанных объектов.
JNI - Java Native Interface — стандартный механизм для запуска кода под
управлением виртуальной машины Java, который написан на unmanaged-языках
и скомпонован в виде динамических библиотек
GC Roots
Из-за того, что minor и major сборки тесно связаны, то нет смысла
разбирать её отдельно, поэтому сразу перейдём к full сборке.
Full сборка
Если молодая сборка не может перевести объект в хранилище
(недостаточно пространства), то запускается full.
Full сборка очищает обе области - и старое поколение, и новое.
В зависимости от того, какой механизм сборки применяется при работе
со старым поколением, может потребоваться перемещать объекты в
старом поколении. Это позволяет гарантировать, что в старом поколении
хватает места, чтобы при необходимости выделить крупный объект.
Сборки происходят не часто, но когда происходит, занимают много
времени.
Сборщики мусора, умеющие работать с такой моделью называются
Generational Garbage Collection, т.е учитывающие поколения.
Реализации GC
В HotSpot VM реализовано несколько сборщиков мусора
основанных на идее Generational Garbage Collection:
• Serial GC
• Parallel GC
• CMS GC
• G1 GC
• Epsilon GC (Java 11+)
• Shenandoah GC (Java 15+)
• ZGC GC (Java 15+)
Serial GC
Это самая простая реализация GC. Она предназначена для
небольших приложений, работающих в однопоточных средах. Все
события сборки мусора выполняются последовательно в одном
потоке. Уплотнение выполняется после каждой сборки мусора.
Serial GC
Запуск сборщика приводит к событию “остановки мира”, когда все
приложение приостанавливает работу. Поскольку на время сборки
мусора все приложение замораживается, не следует прибегать к
такому в реальной жизни, если требуется, чтобы задержки были
минимальными.
Аргумент JVM для использования последовательного сборщика
мусора -XX:+UseSerialGC
Parallel GC
Параллельный сборщик мусора предназначен для приложений со
средними и большими наборами данных, которые выполняются на
многопроцессорном или многопоточном оборудовании. Это
реализация GC по умолчанию, и она также известна как сборщик
пропускной способности.
Несколько потоков предназначаются для малой сборки мусора в
молодом поколении. Единственный поток занят основной сборкой
мусора в старшем поколении.
Parallel GC
Запуск параллельного GC также вызывает “остановку мира”, и
приложение зависает. Такое больше подходит для многопоточной
среды, когда требуется завершить много задач и допустимы
длительные паузы, например при выполнении пакетного задания.
Аргумент JVM для использования параллельного сборщика
мусора: -XX:+UseParallelGC
CMS (Параллельная пометка и зачистка) GC
Concurrent Mark and Sweep Garbage Collector
Также известен как параллельный сборщик низких пауз. Для малой
сборки мусора задействуются несколько потоков, и происходит это
через такой же алгоритм, как в параллельном сборщике. Основная
сборка мусора многопоточна, как и в старом параллельном GC, но
CMS работает одновременно с процессами приложений, чтобы
свести к минимуму события “остановки мира”.
CMS (Параллельная пометка и зачистка) GC
Из-за этого сборщик CMS потребляет больше ресурсов процессора,
чем другие сборщики. Если у вас есть возможность выделить
больше ЦП для повышения производительности, то CMS
предпочтительнее, чем простой параллельный сборщик. В CMS GC
не выполняется уплотнение.
Аргумент JVM для использования параллельного сборщика мусора
с разверткой меток: -XX:+UseConcMarkSweepGC
G1 (Мусор — первым) GC
Garbage first Garbage Collector
G1GC был задуман как замена CMS и разрабатывался для
многопоточных приложений, которые характеризуются крупным
размером кучи (более 4 ГБ). Он параллелен и конкурентен, как CMS,
но “под капотом” работает совершенно иначе, чем старые сборщики
мусора.
Хотя G1 также действует по принципу поколений, в нем нет
отдельных пространств для молодого и старшего поколений. Вместо
этого каждое поколение представляет собой набор областей, что
позволяет гибко изменять размер молодого поколения.
G1 (Мусор — первым) GC
G1 разбивает кучу на набор областей одинакового размера (от 1 МБ
до 32 МБ — в зависимости от размера кучи) и сканирует их в
несколько потоков. Область во время выполнения программы
может неоднократно становиться как старой, так и молодой.
После завершения этапа разметки G1 знает, в каких областях
содержится больше всего мусора. Если пользователь заинтересован
в минимизации пауз, G1 может выбрать только несколько областей.
Если время паузы неважно для пользователя или предел этого
времени установлен высокий, G1 пройдет по большему числу
областей.
G1 (Мусор — первым) GC
G1 (Мусор — первым) GC
Помимо областей Эдема, Выживших и Старой памяти, в G1GC
присутствуют еще два типа.
• Humongous (Огромная) — для объектов большого размера (более
50% размера кучи).
• Available (Доступная) — неиспользуемое или не выделенное
пространство.
Аргумент JVM для использования сборщика мусора G1:
-XX:+UseG1GC
Epsilon GC
Epsilon — сборщик мусора, который был выпущен как часть JDK 11.
Он обрабатывает выделение памяти, но не реализует никакого
реального механизма восстановления памяти. Как только
доступная куча исчерпана, JVM завершает работу 🤡
Его можно задействовать для приложений, чувствительных к
сверхвысокой задержке, где разработчики точно знают объем
памяти приложения или даже добиваются ситуации (почти) полной
свободы от мусора. В противном случае пользоваться Epsilon GC не
рекомендуется.
Epsilon GC
Если вдруг захотите поэкспериментировать и попробовать такой
сборщик, то необходимо включить «экспериментальные»
возможности JDK
-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
Shenandoah GC
Ключевое преимущество Shenandoah перед G1 состоит в том, что
большая часть цикла сборки мусора выполняется одновременно с
потоками приложений. G1 может эвакуировать области кучи только
тогда, когда приложение приостановлено, а Shenandoah перемещает
объекты одновременно с приложением.
Shenandoah может компактировать живые объекты, очищать мусор
и освобождать оперативную память почти сразу после
обнаружения свободной памяти. Поскольку все это происходит
одновременно, без приостановки работы приложения, то
Shenandoah более интенсивно нагружает процессор.
Shenandoah GC
Данный сборщик тоже из разряда экспериментальных, поэтому
Аргумент JVM для сборщика мусора Шенандоа:
-XX:+UnlockExperimentalVMOptions
-XX:+UseShenandoahGC
ZGC
Он предназначен для приложений, которые требуют низкой
задержки (паузы в менее чем 10 мс) и/или задействуют очень
большую кучу (несколько терабайт).
Основные цели ZGC — низкая задержка, масштабируемость и
простота в применении. Для этого ZGC позволяет Java-приложению
продолжать работу, пока выполняются все операции по сбору
мусора. По умолчанию ZGC освобождает неиспользуемую память и
возвращает ее в операционную систему.
ZGC
Таким образом, ZGC
привносит значительное
улучшение по сравнению с
другими традиционными
GCS, обеспечивая
чрезвычайно низкое время
паузы
ZGC
Аргумент JVM для использования сборщика мусора ZGC:
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
Примечание: Хоть и доступен на самом деле ещё в 12 версии JDK,
но был официально релизнут только в 15-ой.
Не предназначен для использования на обычных персональных
компьютерах, только для больших серверных решений с большим
объёмом памяти !
Выбирайте сборщик правильно
Хороший ориентир в плане начальных настроек — характер
настраиваемого приложения. К примеру, параллельный сборщик
мусора эффективен, но часто вызывает события “остановки мира”,
что делает его более подходящим для внутренней обработки, где
допустимы длительные паузы.