Привет! В прошлой лекции мы впервые познакомились со встроенным механизмом языка Java, сборщиком мусора. Коротко: сборщик мусора (garbage collector) работает в фоне параллельно с программой и освобождает память от ненужных объектов. Он не считает ссылки, а делит объекты на достижимые и недостижимые: если до объекта нельзя дойти по цепочке ссылок от кода, он удаляется. Все объекты хранятся в куче (heap), которая делится на Young Generation (Eden и Survival Space) и Old Generation. Он функционирует в фоновом режиме во время работы твоей программы, собирает ставшие ненужными объекты, которые в дальнейшем будут удалены. Таким образом, он освобождает память для создания новых объектов в будущем. В этой лекции мы подробнее разберем принцип его работы. Например, как и в какой момент объект становится ненужным? И как об этом узнает сборщик мусора? На эти вопросы и ответим :) Лекция у нас скорее обзорная: этот материал не надо заучивать наизусть. Он призван скорее расширить твой кругозор касаемо работы памяти и сборщика мусора, поэтому будет достаточно прочесть его и вынести что-то новое для себя :) Поехали!

Кратко

  • Сборщик мусора (garbage collector) работает параллельно с программой и освобождает память от ненужных объектов.
  • Он не считает ссылки, а делит объекты на достижимые и недостижимые.
  • Объект достижим, если до него можно дойти по цепочке ссылок от кода программы; иначе он недостижим и будет удален.
  • Все объекты хранятся в области памяти куча (heap).
  • Куча делится на Young Generation (Eden и Survival Space) и Old Generation (объекты-долгожители).
  • Быстрая сборка охватывает одну область; полная (медленная) запускается, только когда заполнена Old Generation.

Сборщик мусора работает параллельно с программой

Первое, о чем тебе нужно помнить: сборщик мусора работает параллельно с твоей программой. Он не является ее частью и функционирует отдельно: для описания этого мы в прошлой лекции приводили аналогию с роботом-пылесосом. На самом деле, так было не всегда. Раньше сборщик мусора был устроен таким образом, что работал в одном потоке с твоей программой. И по какому-то графику, раз в сколько-то минут, начинал проверку на наличие в программе ненужных объектов. Проблема заключалась в том, что на время этой проверки и сборки мусора программа зависала и не выполнялась. Представь, что ты сидишь в офисе за работой. Но тут приходит уборщица, которой нужно вымыть полы в помещении. Она выгоняет тебя из-за компьютера на 5 минут и ты ждешь, пока она не закончит уборку. В это время ты работать не можешь. Вот примерно так раньше работали сборщики мусора :) Позднее этот механизм изменили, и теперь сборщик мусора работает в фоновом режиме, не тормозя работу самой программы.

Достижимые и недостижимые объекты

Ты уже знаешь, что объект умирает, когда на него не остается ни одной ссылки. Но на самом деле сборщик мусора не считает ссылки на объекты. Во-первых, это может быть достаточно долго. Во-вторых, не очень эффективно. Ведь объекты могут ссылаться друг на друга! Три объекта ссылаются друг на друга, но недостижимы для остальной программыНа рисунке изображен пример, когда 3 объекта ссылаются друг на друга, но больше на них не ссылается никто. То есть для работы остальной части программы они не нужны. Если бы сборщик мусора занимался просто подсчетом ссылок, все эти 3 объекта остались и не освободили бы память: ссылки-то на них есть! Это можно сравнить с космическим кораблем. Космонавты во время полета решили проверить список запчастей для ремонта и обнаружили среди них руль и педали от обычного автомобиля. Они явно тут не нужны и занимают лишнее место. Хоть эти детали связаны и имеют какие-то функции, в рамках работы космического корабля это ненужный мусор, от которого лучше избавиться. Поэтому в Java приняли решение сделать основой для сборки мусора не подсчет ссылок, а разделение объектов на два вида: достижимые и недостижимые. Как определить, является ли объект достижимым? Все гениальное просто. Объект считается достижимым, если на него ссылается другой достижимый объект. Получается такая "цепочка достижимости". Она начинается при запуске программы и тянется в течение всего времени ее работы. Это выглядит примерно вот так: Цепочка достижимости: синие объекты достижимы, красные недостижимы и будут удаленыСтрелка на рисунке обозначает выполняемый код нашей программы. В коде, например, в методе main(), создаются ссылки на объекты. Эти объекты могут ссылаться на новые объекты, те на какие-то еще и так далее. Образуется цепочка ссылок объектов. Если от объекта по этой цепочке ссылок можно прийти к "корневой ссылке", то есть той, которая непосредственно создается в выполняемом коде, он считается достижимым. У нас на картинке они обозначены синим цветом. А вот если объект выпал из этой цепочки, то есть ни одна из переменных в коде, выполняемом в данный момент, не содержит ссылок на него, и по "цепочке ссылок" до него тоже невозможно добраться, он считается недостижимым. В нашей программе два таких объекта обозначены красным цветом. Обрати внимание: у этих "красных" объектов есть ссылки друг на друга. Но, как мы и сказали ранее, современный сборщик мусора в Java не занимается подсчетом ссылок. Он определяет достижимость либо недостижимость объекта. Поэтому два красных объекта на рисунке станут его добычей.

Как устроена куча: Eden, Survival Space и Old Generation

Теперь рассмотрим весь процесс от начала до конца, а заодно посмотрим, как устроена память в Java :) Все объекты в Java хранятся в специальной области памяти, которая называется куча (heap). Объекты попадают в кучу в момент создания через new (подробнее о создании объектов в статье про классы и объекты). В обычном языке "кучей" называют гору предметов, где все подряд валяется вперемешку. Но куча в Java не такая. Она имеет очень логичную и разумную структуру. В один прекрасный день программисты Java обнаружили, что все объекты в их программах можно разделить на два типа: условно говоря, простые объекты и "долгожители". "Долгожителями" считаются объекты, пережившие много сборок мусора. Чаще всего они будут существовать до конца работы программы. В итоге общая куча, где хранятся все созданные объекты, была разделена на несколько частей. Первая часть имеет красивое название, Eden (библ. "Райский сад"). Это отличное название, ведь именно сюда объекты попадают после их создания. Именно в этой части выделяется память для новых объектов, когда мы пишем new. Объектов может создаться много, и когда в этой области заканчивается место, начинается первая, "быстрая" сборка мусора. Надо сказать, что сборщик мусора очень умен и выбирает алгоритм работы в зависимости от того, чего в куче больше, мусора или рабочих объектов. Если почти все объекты являются мусором, сборщик помечает "живые" объекты и переносит их в другую область памяти, после чего текущая область очищается полностью. Если же мусора мало и большую часть занимают живые объекты, он помечает мусор, очищает его, а остальные объекты компонует. Мы сказали "сборщик помечает "живые" объекты и переносит их в другую область памяти", но в какую? Область памяти, куда переносятся все объекты, пережившие хотя бы одну сборку мусора, называется Survival Space ("место для выживших"). Survival Space в свою очередь делится на поколения. Каждый объект относится к своему поколению в зависимости от того, сколько сборок мусора он пережил. Если одну, он относится к "Поколению 1", если 5, к "Поколению 5". Вместе Eden и Survival Space образуют область под названием Young Generation ("молодое поколение"). Помимо Young Generation в куче существует и другая область памяти, Old Generation ("старое поколение"). Сюда как раз попадают те самые объекты-долгожители, которые пережили много сборок мусора. Их выгоднее хранить отдельно от всех остальных.

Быстрая и полная сборка мусора

И только когда область Old Generation заполнена, т.е. даже объектов-долгожителей в программе так много, что памяти не хватает, производится полная уборка мусора. Она обрабатывает не одну область памяти, а вообще все созданные Java-машиной объекты. Естественно, она занимает куда больше времени и ресурсов. Именно поэтому объекты-долгожители было решено хранить отдельно. Когда место заканчивается в других областях, проводится так называемая "быстрая сборка мусора". Она охватывает только одну область, и за счет этого является более экономичной и быстрой. В конце, когда забита уже даже область для долгожителей, в бой вступает полная уборка. Таким образом, самый "тяжеловесный" инструмент используется сборщиком только тогда, когда без этого уже не обойтись. Схематично структура кучи и уборки выглядят так: Структура кучи в Java: Eden, Survival Space, Old Generation и сборка мусораСведем области кучи в одну таблицу:
ОбластьЧто хранитКогда собирается
Edenновые объекты сразу после newпри заполнении, быстрая сборка
Survival Spaceобъекты, пережившие хотя бы одну сборку (делится на поколения)при быстрой сборке
Young GenerationEden и Survival Space вместебыстрая сборка
Old Generationобъекты-долгожители (пережили много сборок)полная сборка, когда область заполнена
Если проследить путь одного объекта по куче, он выглядит так:
  1. Объект создается через new и попадает в Eden.
  2. Когда Eden заполняется, запускается быстрая сборка: выжившие объекты переезжают в Survival Space.
  3. За каждую пережитую сборку объект переходит в следующее поколение внутри Survival Space.
  4. Объекты, пережившие много сборок (долгожители), перемещаются в Old Generation.
  5. Когда заполняется и Old Generation, запускается полная сборка мусора по всей куче.
Если хочешь копнуть глубже, работа сборщика мусора описана в материалах Oracle (Java Garbage Collection Basics).

Читайте также

Вопросы и ответы

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

Это встроенный механизм, который в фоновом режиме находит ненужные (недостижимые) объекты и освобождает занятую ими память. Работает параллельно с программой.

Как сборщик мусора определяет, что объект не нужен?

Он не считает ссылки, а проверяет достижимость: если до объекта можно дойти по цепочке ссылок от выполняемого кода, объект достижим; если нет, он недостижим и будет удален.

Почему подсчет ссылок не используется?

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

Что такое куча (heap) в Java?

Это область памяти, где хранятся все объекты. Она разделена на части: Young Generation (Eden и Survival Space) и Old Generation.

Чем Young Generation отличается от Old Generation?

В Young Generation (Eden и Survival Space) попадают новые и недавно созданные объекты. В Old Generation хранятся долгожители, пережившие много сборок мусора.

Чем быстрая сборка мусора отличается от полной?

Быстрая сборка охватывает только одну область (например Eden) и работает быстро. Полная сборка обрабатывает всю кучу, включая Old Generation, и запускается редко, когда памяти уже не хватает.