Как работает Garbage Collector и что такое минорная сборка мусора
Что проверяют
Понимаете ли вы, что сборщик ищет живые объекты по достижимости от корней, и зачем память делят на поколения. Для Android важно ещё, что там работает не HotSpot, а ART, и следующий вопрос обычно про утечки Activity и подтормаживания из-за аллокаций.
Как отвечать
Сначала общий механизм:
- достижимость: от корней (стеки потоков, статические поля, JNI-ссылки) обходится граф ссылок. Всё, до чего не дошли, считается мусором, циклические ссылки тоже собираются;
- гипотеза поколений: большинство объектов умирает молодыми, поэтому молодую область собирают часто и дёшево, старую редко;
- минорная сборка: в классической JVM это сборка молодого поколения. Живые объекты из Eden и одного Survivor копируются во второй Survivor, пережившие несколько сборок переезжают в старое поколение. Стоимость зависит от числа живых объектов, а не мусора, поэтому при короткоживущих объектах она дешёвая. Ссылки из старого поколения в молодое учитываются через card table, чтобы не сканировать всю кучу;
- major и full GC: сборка старого поколения или всей кучи, долгая и дорогая.
Теперь про Android. ART собирает мусор в основном конкурентно: работу делает фоновый поток, а приложение останавливается на короткие паузы. Сборщики менялись: с Android 8 основным стал Concurrent Copying, в свежих версиях через обновляемый модуль ART приходит Concurrent Mark-Compact. У ART тоже есть частые лёгкие сборки только недавно выделенных объектов, это и есть аналог минорной сборки. В Logcat сборки видны строками вида ... GC freed ... с объёмом освобождённой памяти и паузой.
На практике аллокации в onDraw или в onBindViewHolder вызывают частые сборки и пропуски кадров. Смотрят это в Memory Profiler Android Studio, утечки ищут через LeakCanary.
Где спотыкаются
Описывают HotSpot с флагами -Xmx и G1, забывая, что в Android другая виртуальная машина и другие сборщики.
Считают, что GC убирает «неиспользуемые» объекты. Он убирает недостижимые: Activity, на которую ссылается статическое поле или неотписанный листенер, продолжит жить, это и есть утечка.
Лечат память вызовом System.gc(). Это лишь подсказка системе, утечку он не исправит.