— Привет! Снова решила устроить тебе небольшую лекцию про сборку мусора.

Как ты уже знаешь, Java-машина сама отслеживает ситуации, когда объект становится ненужным и удаляет его.

— Ага. Вы с Ришей раньше мне рассказывали об этом, нюансов я не помню.

— Ок. Тогда повторим.

Сборка мусора - 1

Как только объект создается, Java выделяет ему память. А за востребованностью объекта следит с помощью переменных-ссылок. Объект может быть удален при «сборке мусора» — процедуре очистки памяти, если не остается переменных, которые ссылаются на этот объект.

— А расскажи немного о сборщике мусора, что это такое и как он работает.

— Ок. Раньше сборка мусора происходила в главном потоке/нити. Раз в 5 минут, или чаще. Если наступал случай, когда не хватало свободной памяти, Java-машина приостанавливала работу всех нитей и удаляла неиспользуемые объекты.

Но сейчас от этого подхода отказались. Сборщик Мусора нового поколения работает незаметно и в отдельном потоке. Такую сборку мусора принято называть параллельной.

— Ясно. А как именно определяется – нужно удалять объект или нет.

— Просто считать количество ссылок на объект не очень эффективно – ведь могут быть объекты, которые ссылаются друг на друга, но больше на них не ссылается никто.

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

— Ок. С этим вроде ясно.

А как происходит сама уборка мусора – удаление ненужных объектов?

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

— Интересный подход. И не надо считать ссылки – скопировал все достижимые объекты, а все остальные – мусор.

— Там все немного сложнее. Программисты Java выяснили, что объекты обычно делятся на две категории – долгоживущие (которые существуют все время работы программы) и маложивущие (нужны в методах и для выполнения «локальных» операций).

Хранить долгоживущие отдельно от маложивущих гораздо эффективнее. Для этого надо было придумать механизм определения долгожительства объекта.

Поэтому они разделили всю память на «поколения». Есть объекты первого поколения, есть объекты второго поколения и т.д. Каждый раз после очистки памяти счетчик поколений увеличивается на 1. Если какие-то объекты существуют много поколений, то их записывали в долгожители.

Сборщик Мусора очень — сложная и эффективная составляющая Java. Многие его части работают эвристически – на основе алгоритмов-догадок. Поэтому он часто «не слушается» пользователя.

— В смысле?

— У Java есть объект GC (Garbage Collector – Сборщик Мусора), который можно вызвать с помощью метода System.gc().

Также можно принудительно инициировать вызов finalize-методов удаляемых объектов, посредством System.runFinalization(). Но дело в том, что по документации Java, это не гарантирует ни начало сборки мусора, ни вызов методов finalize(). Garbage Collector сам решает, что и когда ему вызывать.

— Ничего себе! Буду знать.

— Но и это еще не все. Как ты знаешь, в Java одни объекты ссылаются на другие, и именно с помощью этой сети ссылок определяется – стоит удалять объект или нет.

Так вот, в Java есть специальные ссылки, которые позволяют влиять на этот процесс. Для них есть специальные классы-обертки. Вот они:

SoftReference – мягкая ссылка.

WeakReference – слабая ссылка.

PhantomReference – призрачная ссылка.

— М-да. Чем-то напоминает внутренние классы, вложенные классы, внутренние анонимные классы, локальные классы. Названия разные, но по ним совсем не понятно за что они отвечают.

— Вот ты, Амиго, и стал программистом. Теперь ты возмущаешься по поводу названий классов – дескать, недостаточно информативны, и нельзя по одному названию(!) определить, что этот класс делает, как и зачем.

— Ого. А я и сам не заметил. Но это же так очевидно.

Ладно. Соловья баснями не кормят. Давай я тебе расскажу про SoftReference – мягкие ссылки.

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

Пример такой ссылки:

Пример
//создание объекта Cat
Cat cat = new Cat();

//создание мягкой ссылки на объект Cat
SoftReference<Cat> catRef = new SoftReference<Cat>(cat);

//теперь на объект ссылается только мягкая ссылка catRef.
cat = null;

//теперь на объект ссылается еще и обычная переменная cat
cat = catRef.get();

//очищаем мягкую ссылку
catRef.clear();

Если на объект существуют только мягкие ссылки, то он продолжает жить и называется «мягкодостижимым».

Но! Объект, на который ссылаются только мягкие ссылки, может быть удален сборщиком мусора, если программе не хватает памяти. Если программе вдруг не хватает памяти, прежде чем выкинуть OutOfMemoryException, сборщик мусора удалит все объекты, на которые ссылаются мягкие ссылки и попробует выделить программе память еще раз.

Предположим, что программа-клиент часто запрашивает у программы-сервера различные данные. Тогда программа сервер может некоторые из них кэшировать, воспользовавшись для этого SoftReference. Если объекты, удерживаемые от смерти мягкими ссылками, будет занимать большую часть памяти, то сборщик мусора просто их поудаляет и все. Красота!

— Ага. Мне самому понравилось.

— Ну и маленькое дополнение: у класса SoftReference есть два метода. Метод get() возвращает объект, на который ссылается SoftReference. Если объект был удален сборщиком мусора, внезапно(!) метод get() начнет отдавать null.

Так же пользователь может сам очистить SoftReference, вызвав метод clear(). При этом слабая ссылка внутри объекта SoftReference будет уничтожена.

На этом пока все.

— Спасибо за интересный рассказ, Элли. Действительно было очень интересно.