Сборщик мусора: как JVM освобождает память
Разбираем, почему в Java не нужно вручную освобождать память — и как устроен механизм, который делает это за вас.
Сборщик мусора (Garbage Collector, GC) — часть JVM, которая автоматически находит объекты, на которые больше никто не ссылается, и освобождает занятую ими память.
Что выведет этот код?
Начнём с примера, который заставляет задуматься о том, что вообще происходит с памятью в Java:
public class Main {
public static void main(String[] args) {
String text = new String("Привет");
System.out.println(text);
text = null;
System.out.println("Ссылка обнулена, но программа работает дальше");
}
}Вывод:
Привет
Ссылка обнулена, но программа работает дальшеНичего страшного не происходит — программа просто продолжает работать. Но вопрос на собеседовании звучит иначе: «Что произошло с объектом String, который раньше хранился в text, после строки text = null?» И вот тут начинается самое интересное.
Почему в Java не бывает free() и delete
Если вы хоть немного знакомы с C или C++, то знаете, что там программист сам обязан освобождать память, которую выделил — через free() или delete. Забыл освободить — получил утечку памяти. Освободил дважды или слишком рано — получил крах программы.
В Java такого нет вообще. Разработчик создаёт объекты через new, но никогда не пишет код вида «удали этот объект». Этим занимается сборщик мусора — отдельный механизм внутри JVM, который работает в фоне, параллельно с вашей программой.
Когда объект становится «мусором»
Объект в Java становится кандидатом на удаление, когда на него больше не осталось ни одной живой ссылки из работающей части программы. Это называется «объект недостижим» (unreachable).
public class Main {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("данные");
System.out.println(builder);
builder = null;
// теперь объект StringBuilder("данные") недостижим —
// на него не осталось ни одной ссылки, GC может его удалить
System.gc(); // «просьба» к JVM запустить сборку мусора
}
}Важная деталь: System.gc() — это не команда «удали прямо сейчас», а лишь просьба к JVM. Сборщик мусора сам решает, когда именно запускаться — сразу, чуть позже или вообще проигнорировать вызов, если посчитает, что памяти достаточно. Разработчик не контролирует момент удаления напрямую, и это специально: JVM видит общую картину использования памяти лучше, чем отдельный кусок кода.
Разберём ещё один пример — объект может стать недостижимым не только через null, но и когда переменная просто выходит из области видимости:
public class Main {
static void createObject() {
StringBuilder local = new StringBuilder("временный");
System.out.println(local);
} // здесь метод завершается, переменная local исчезает,
// объект StringBuilder("временный") становится недостижимым
public static void main(String[] args) {
createObject();
System.out.println("Метод завершён, объект внутри него уже недостижим");
}
}Вывод:
временный
Метод завершён, объект внутри него уже недостижимКак это работает под капотом: идея поколений
Самое интересное — как именно JVM ищет объекты-мусор, не тормозя при этом всю программу. Один из ключевых приёмов называется generational garbage collection (сборка мусора по поколениям), и в его основе лежит простое наблюдение из практики: большинство объектов «умирают молодыми». Временные переменные, промежуточные строки, объекты внутри одного метода — почти все они становятся мусором очень быстро. А вот объекты, которые пережили много циклов сборки, обычно продолжают жить ещё долго (например, кеши или списки, созданные при старте программы).
Исходя из этого, память в куче JVM (heap) делится на зоны:
| Зона | Что там хранится |
| Young Generation (молодое поколение) | только что созданные объекты — сюда попадает почти всё |
| Old Generation (старое поколение) | объекты, которые «пережили» несколько сборок мусора в молодом поколении |
Логика простая: сборка мусора в Young Generation происходит часто, но быстро — там объектов много, но проверять их дёшево, потому что зона небольшая. Когда объект переживает несколько таких проверок подряд (то есть на него всё ещё есть ссылки), JVM «повышает» его в Old Generation. А там сборка мусора запускается гораздо реже, потому что объекты, дожившие до старого поколения, с высокой вероятностью проживут ещё долго — незачем проверять их постоянно.
Благодаря такому разделению JVM не обязана сканировать всю память при каждой проверке — это и делает автоматическое управление памятью в Java достаточно быстрым для промышленной разработки.
Частые ошибки на собеседовании
- Говорят, что
System.gc()гарантированно немедленно запускает сборку мусора. Это лишь рекомендация JVM, а не команда. - Путают присвоение
nullс реальным удалением объекта —nullлишь обнуляет ссылку, а сам объект удалится позже, когда до него дойдёт очередь у сборщика. - Не могут объяснить, зачем нужно деление на поколения, и говорят просто «для оптимизации» без понимания сути: большинство объектов живут очень недолго, и с этим наблюдением выгодно работать отдельно.
- Считают, что в Java невозможны утечки памяти. Возможны — например, если объект случайно остаётся достижимым (лежит в статической коллекции, из которой его забыли удалить), сборщик мусора его не тронет, даже если он фактически больше не нужен программе.
Итоги-шпаргалка
- В Java не нужно вручную освобождать память — этим занимается сборщик мусора внутри JVM.
- Объект становится кандидатом на удаление, когда на него не остаётся ни одной живой ссылки (объект «недостижим»).
System.gc()— лишь просьба к JVM, а не гарантия немедленной сборки мусора.- Куча делится на Young Generation (новые объекты, частая быстрая сборка) и Old Generation (долгоживущие объекты, редкая сборка) — это ускоряет работу GC.
- Утечки памяти в Java всё же возможны, если объект случайно остаётся достижимым дольше, чем нужно.