Прокачайся в код-ревью: для первых 50 участников — курс бесплатный

время чтения: 6 мин

Что такое сборка мусора в Java и как она работает?

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

Автор статьи — Senior Software Engineer EPAM Вайбхави Дешпанде.

Читать другие статьи эксперта:

Цель сборки мусора в Java

Цель сборки мусора в Java — автоматическое управление памятью путем ее освобождения в случае, когда она занята неиспользуемыми объектами. Это помогает предотвратить утечки памяти и снижает нагрузку на ручное управление ей. Сборка мусора предусматривает механизм для определения и очистки ненужных объектов и освобождение памяти для размещения новых объектов.

Эволюция сборки мусора

Перед тем, как сборка мусора появилась в Java, языки программирования, такие как C и C++, нуждались в ручном управлении памятью. Разработчикам приходилось специально выделять и освобождать память для объектов, используя функции malloc() и free(). Ручное управление памятью часто приводило к проблемам: утечкам памяти, висячим указателям и ошибкам сегментации. Это был сложный процесс с высокой вероятностью ошибок, который требовал тщательного отслеживания процессов выделения и освобождения памяти — аллокации и деаллокации.

Одним из основных достижений Java стало создание автоматической сборки мусора. В виртуальной машине Java (JVM) есть сборщик мусора, который автоматически управляет памятью от лица разработчика. Сборщик мусора определяет и освобождает память, занятую объектами, которые больше не доступны или не используются программой. Такое автоматическое управление памятью упрощает процесс разработки, снижает вероятность багов, связанных с памятью, и повышает общую стабильность программы.

Эволюция сборки мусора в Java привела к усовершенствованию ее алгоритмов, методов оптимизации и возможностей настройки в соответствии с требованиями разных приложений. Со временем в JVM было внедрено несколько сборщиков мусора с различными характеристиками и компромиссами, позволяющих разработчикам выбрать наиболее подходящий сборщик в зависимости от потребностей приложения.

Изначально JVM использовала простой сборщик мусора, работающий по алгоритму пометок (англ. mark-sweep), который помечал достижимые объекты и убирал недостижимые. Однако при таком подходе возникали проблемы фрагментации памяти. Впоследствии в JVM был введен алгоритм компактизации (англ. mark-compact) с фазой сжатия, чтобы снизить фрагментацию памяти.

Java-приложения постепенно становились сложнее, поэтому возникла необходимость в более эффективных механизмах сборки мусора. В JVM была представлена сборка мусора по поколениям, которая в зависимости от возраста объекта разделяет кучу (англ. Java heap) — одну из областей памяти — на молодое и старое поколения. Этот способ оптимизирует сборку мусора путем более частого и быстрого сбора в молодом поколении и менее частого —в старом.

Далее в JVM внедрили сборщики мусора, ориентированные на разные сценарии применения. В современных JVM есть следующие сборщики мусора: Serial Collector, Parallel Collector, Concurrent Mark Sweep (CMS) Collector и Garbage-First (G1) Collector. Они предлагают различные компромиссы между пропускной способностью, латентностью и использованием памяти, позволяя разработчикам выбрать наиболее подходящий сборщик для своего приложения.

Появление сборки мусора в Java стало революционным в вопросе управления памятью. Это помогло отойти от ручного управления, снизить количество багов, связанных с памятью, и улучшить стабильность приложений. Развитие сборки мусора в Java также привело к разработке более сложных алгоритмов, сборщиков мусора и возможностей настройки, чтобы удовлетворить разнообразные требования приложений.

10 интересных фактов о сборке мусора в Java

1. HotSpot JVM

HotSpot JVM, разработанная Sun Microsystems (сейчас принадлежит Oracle), — одна из самых широко используемых виртуальных машин для выполнения Java-приложений. В ней предусмотрены различные алгоритмы сборки мусора и сборщики для оптимизации управления памятью.

2. Stop-the-world паузы

Во время сборки мусора JVM обычно приостанавливает потоки выполнения приложения. Эти так называемые stop-the-world паузы временно останавливают процессы в приложении. Однако современные сборщики мусора стремятся минимизировать длительность и частоту таких пауз, чтобы избежать значительных сбоев.

3. Параллельная сборка мусора

Concurrent Mark Sweep (CMS) и Garbage-First (G1) — примеры сборщиков мусора, которые производят сборку параллельно с выполнением потоков приложения. Они стремятся минимизировать stop-the-world паузы, выполняя в приложении операции по сборке мусора одновременно с кодом.

4. Фрагментация памяти

Фрагментация памяти может возникать в куче, когда свободная память разделена на небольшие несмежные блоки. Это может привести к неэффективному использованию памяти и увеличению нагрузки на ее выделение. Сборщики мусора, выполняющие сжатие, например, алгоритм mark-compact, помогают снизить фрагментацию. Это происходит путем перераспределения памяти для создания смежных блоков свободной памяти.

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

Сборка мусора по поколениям основана на наблюдении, что большинство объектов становятся мусором вскоре после создания. Разделяя кучу на молодое и старое поколение, JVM оптимизирует сборку мусора, чаще и быстрее собирая молодые объекты, а старые — реже.

6. Настройка и кастомизация

Сборщики мусора в JVM часто предусматривают различные опции настройки и параметры для кастомизации их поведения. Разработчики могут настраивать эти параметры, чтобы оптимизировать производительность сборки мусора в соответствии с конкретными характеристиками и требованиями приложения.

7. Алгоритмы сборки мусора

Наряду с упомянутыми ранее сборщиками, JVM также использует различные алгоритмы сборки мусора: пометок, компактизации и копирования. Они определяют, как объекты идентифицируются, помечаются и восстанавливаются во время сборки.

8. Влияние на производительность приложения

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

9. Утечки памяти

Хотя сборка мусора помогает предотвратить многие утечки памяти, она не исключает возможность их возникновения. Утечки памяти могут происходить, если объекты случайно остаются доступными по strong references, или если ресурсы не освобождаются должным образом. Чтобы этого избежать, необходимо применять соответствующие методы программирования и управления памятью.

10. Постоянные исследования и улучшения

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

Сборка мусора в Java — это сложный и интересный аспект управления памятью. Понимание ее принципов и оптимизаций может значительно помочь вам создавать эффективные и надежные Java-приложения.

Как работает сборка мусора в Java

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

  • Достижимость объектов

Сборщик мусора определяет, какие объекты все еще используются, а какие подходят для сборки. Он начинает рассматривать набор корневых объектов — статические переменные, стек вызова и локальные переменные. Любой объект, на который непосредственно или косвенно ссылаются эти корневые объекты, считается достижимым и не подлежит сборке мусора. Объекты, которые не доступны из корневых объектов, считаются недостижимыми и могут быть собраны. Для определения достижимости объектов в Java используется структура на основе теории графов.

Пример: Представьте себе приложение социальной сети, где пользователи могут создавать посты и оставлять комментарии к ним. Каждая запись и комментарий представлены объектом. Корневыми объектами в этом сценарии могут быть профиль активного пользователя, написанные им посты и оставленные комментарии. Любой объект, на который непосредственно или косвенно ссылаются эти корневые объекты, будет считаться достижимым и не будет подлежать сборке мусора.

  • Алгоритм пометок

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

Пример:

Как создать несколько экземпляров класса MyClass

В приведенном выше коде мы создаем несколько экземпляров класса MyClass. Мы также создаем ссылки obj4 и obj5, которые указывают на obj1 и obj2 соответственно. Затем мы присваиваем obj1, obj2 и obj3 значение null, чтобы они стали доступными для сборки мусора. Наконец, мы напрямую вызываем метод System.gc() для запроса сборки мусора.

Когда сборщик мусора работает, он помечает все достижимые объекты, начиная с корневых (obj4 и obj5). Поскольку obj1, obj2 и obj3 больше не являются достижимыми, они будут определены как недостижимые на фазе очистки. В результате их память будет восстановлена, и для каждого из этих объектов будет вызван метод finalize().

  • Финализация

Прежде чем объект будет удален сборщиком мусора, JVM вызывает метод finalize(). Этот метод позволяет объекту выполнить необходимые операции по очистке перед его освобождением из памяти. Однако важно отметить, что не рекомендуется полагаться на finalize() для серьезной очистки ресурсов, так как нельзя гарантировать, что этот метод будет вызван — немедленно или вообще.

Пример: Предположим, у вас есть объект подключения к базе данных, который нужно правильно закрыть перед сборкой мусора для освобождения всех накопленных ресурсов. В метод finalize() можно вставить код, чтобы завершить соединение с базой данных.

Как завершить соединение с базой данных

В приведенном выше коде метод finalize() класса DatabaseConnection переопределяется для вызова метода close(). Это нужно, чтобы правильно закрыть подключение к базе данных, перед тем как сборщик мусора удалит данный объект.

Выводы

Таким образом, сборка мусора в Java включает в себя определение и восстановление памяти, занимаемой недостижимыми объектами. Для определения этих объектов применяется алгоритм пометок. Для очистки объектов перед сборкой мусора может использоваться метод finalize(). Понимая, как работает сборка мусора, Java-разработчики могут писать эффективный и безопасный для памяти код.