Автоматическое управление памятью через сборку мусора — ключевой механизм платформы Android, основанный на виртуальной машине ART. По данным Google Android Documentation, 2026, сборщик мусора освобождает разработчика от ручного управления памятью, автоматически удаляя объекты, на которые больше нет ссылок. Без GC каждое выделение объекта требовало бы явного вызова free или delete, что в Java-экосистеме с миллионами объектов в секунду невозможно физически.
Главное
Garbage Collection (GC) — это автоматический процесс обнаружения и освобождения памяти, занятой объектами, которые больше не используются программой. В контексте мобильной разработки GC применяется в платформе Android через виртуальную машину ART, а также в стандартной Java Virtual Machine.
В отличие от языков с ручным управлением памятью (C, C++), где программист обязан явно вызывать free или delete, GC полностью берёт на себя задачу отслеживания жизненного цикла объектов. Разработчик создаёт новые объекты через оператор new, а сборщик определяет момент, когда объект становится недостижимым — то есть на него не осталось ни одной активной ссылки.
Основная метрика эффективности GC — время паузы (pause time) и пропускная способность (throughput). Пауза — это отрезок, на который приостанавливается выполнение приложения для проведения сборки. В мобильной среде паузы дольше 8–16 миллисекунд заметны как пропущенные кадры (jank).
По данным Google I/O 2019, ART в Android 10 сократила типичные паузы GC до 2–4 мс, что на 70% меньше по сравнению с Dalvik в Android 4.4. Тем не менее, неправильная работа с памятью — частая аллокация объектов в циклах, создание временных экземпляров без необходимости — остаётся основной причиной проблем производительности.
Все реализации GC в Java и Android базируются на нескольких фундаментальных алгоритмах, которые комбинируются для достижения баланса между временем паузы и полнотой очистки. Понимание этих алгоритмов необходимо для написания GC-friendly кода.
Mark-and-Sweep — простейший алгоритм, работающий в два этапа. На этапе Mark сборщик обходит граф объектов, начиная с корневых ссылок (root set) — локальных переменных, статических полей, стеков потоков. Каждый достижимый объект помечается флагом live. На этапе Sweep сборщик проходит по всей куче и освобождает память непомеченных объектов.
Недостаток — фрагментация памяти: после Sweep свободные участки чередуются с занятыми, что затрудняет выделение крупных объектов. В мобильных сценариях это критично, так как куча обычно мала (64–512 МБ на Android).
Copying Collection делит кучу на два полупространства (semi-spaces). Активные объекты копируются из одного полупространства в другое компактно, без разрывов. После копирования старое полупространство целиком объявляется свободным. Алгоритм полностью устраняет фрагментацию, но требует вдвое больше памяти.
В мобильных средах Copying Collection применяется поколенческими сборщиками для быстрой очистки молодых объектов, которые статистически умирают рано (гипотеза слабого поколения).
Generational Collection делит кучу на поколения: Young Generation (молодые объекты) и Old Generation (старые, пережившие несколько сборок). Сборка молодого поколения (Minor GC) выполняется часто и быстро, так как большинство объектов умирают молодыми. Сборка старого поколения (Major GC или Full GC) происходит реже, но длится дольше.
// Демонстрация поколенческого GC: молодые объекты умирают быстро
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // живёт весь метод
for (Item item : items) {
Result r = new Result(item.getValue()); // умирает мгновенно
if (r.isValid()) {
process(r); // r становится мусором
}
}
saveResults(results); // results переходит в Old Gen
}
В этом примере объекты Result создаются внутри цикла и немедленно становятся мусором — они идеальные кандидаты для Young GC. Объект results живёт дольше и мигрирует в Old Generation. Разделение поколений позволяет Minor GC очищать молодые объекты за миллисекунды, не трогая старую кучу.
Android прошёл путь от Dalvik VM до ART (Android Runtime), и реализация GC — одно из ключевых отличий между ними. Понимание архитектуры GC в Android помогает писать код, который минимизирует паузы на реальных устройствах.
| Характеристика | Dalvik (до 4.4) | ART (5.0+) |
|---|---|---|
| Тип GC | Mark-and-Sweep с Concurrent Mark | Generational + Concurrent |
| Типичная пауза | 10–30 мс | 2–4 мс |
| Компактизация | Нет (только фрагментация растёт) | Есть (в фоне, без остановки приложения) |
| AOT-компиляция | JIT (Just-In-Time) | AOT + JIT (гибрид) |
Dalvik использовал комбинацию Mark-and-Sweep с concurrent-фазой. Concurrent Mark позволял приложению продолжать работу во время обхода графа объектов, но этап Sweep требовал остановки всех потоков (Stop-The-World). На устройствах с малым объёмом RAM (512 МБ — 1 ГБ) паузы достигали 30 мс, что вызывало заметные подтормаживания интерфейса. Кроме того, Dalvik не компактизировал кучу, поэтому после длительной работы росла фрагментация, и выделение крупных объектов (например, Bitmap) могло выбросить OutOfMemoryError при достаточном общем объёме свободной памяти.
ART (Android Runtime) внедрил поколенческий сборщик с concurrent-компактизацией. Куча делится на три региона: Young, Mature (аналог Old Generation) и Large Object Space (для объектов размером более 12 КБ). Сборка Young Region происходит параллельно без остановки потоков в большинстве случаев. В Android 10+ появилась Concurrent Copying — компактизация выполняется в фоновом потоке без Stop-The-World.
Благодаря архитектуре ART, типичные паузы GC сократились до 2–4 мс, а в сценариях с преобладанием молодых объектов — до 0.5–1 мс. Это позволило Android-устройствам обеспечивать стабильные 60 FPS даже при активной работе с памятью.
В экосистеме Java существует несколько реализаций GC, каждая со своим профилем производительности. Для Android-разработки выбор ограничен ART, но знание Java GC полезно при написании серверной части мобильных приложений и при разработке на Kotlin Multiplatform.
Serial GC — однопоточный сборщик с полной остановкой приложения (Stop-The-World). Каждая операция Mark, Sweep и Compact выполняется одним потоком. Производительность низкая — для мобильных серверов не применяется. Подходит только для небольших приложений с кучей до 100 МБ.
Parallel GC (также known as Throughput Collector) использует несколько потоков для всех фаз сборки. Ориентирован на максимальную пропускную способность (throughput) — минимизирует время, затрачиваемое на GC относительно времени работы приложения. Включается через флаг -XX:+UseParallelGC в JVM.
G1 (Garbage-First) GC — дефолтный сборщик в Java 9+. Куча делится на регионы по 1–32 МБ. G1 прогнозирует время паузы и стремится уложиться в заданный лимит (по умолчанию 200 мс). Priority: сначала очищаются регионы с наибольшим объёмом мусора (отсюда название). G1 эффективен для серверов с большой кучей (4–64 ГБ) и предсказуемыми паузами.
// Включение G1 GC с целевой паузой 100 мс
// java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar
public class MemoryMonitor {
private static final long THRESHOLD = 512 * 1024 * 1024; // 512 MB
public void checkHeapUsage() {
Runtime rt = Runtime.getRuntime();
long used = rt.totalMemory() - rt.freeMemory();
if (used > THRESHOLD) {
System.out.println("Heap usage exceeded threshold: " + used);
System.out.println("Consider reducing allocations");
}
}
}
Мониторинг кучи через Runtime позволяет детектить утечки памяти на ранней стадии. Если used превышает 80% от максимальной кучи при стабильной работе — это сигнал о возможной утечке или избыточном потреблении памяти приложением.
Даже современный ART GC не решает всех проблем — неправильное использование памяти остаётся главной причиной jank и ANR (Application Not Responding). Рассмотрим основные сценарии и методы оптимизации.
GC Pauses — остановки потоков приложения на время сборки. На экране это проявляется как пропущенные кадры, когда время между двумя кадрами превышает 16.6 мс (60 FPS). Если GC длится 30 мс, рисуется только один кадр вместо двух — пользователь видит подёргивание интерфейса.
Основные причины длинных пауз: большое количество живых объектов в Old Generation, фрагментация кучи, частые Full GC. Для диагностики используются Android Studio Profiler и systrace.
Главное правило GC-friendly кода — минимизировать количество аллоцируемых объектов. Каждый новый объект требует не только выделения памяти, но и последующей сборки. Даже если GC быстр, 1000 лишних аллокаций в секунду дают 1000 проверок сборщику.
Утечка памяти возникает, когда объект остаётся достижимым, хотя больше не нужен. GC не может удалить такой объект, и память постепенно исчерпывается. Типичные причины: не отписанные слушатели, статические ссылки на Activity, анонимные классы, захватывающие внешний контекст, и незакрытые Cursor/InputStream.
// Утечка памяти: анонимный класс держит ссылку на Activity
public void startTask() {
new Thread(new Runnable() { // неявно держит this (Activity)
@Override
public void run() {
// долгая операция...
System.out.println("Done");
}
}).start();
}
// Исправление: статический вложенный класс + WeakReference
private static class TaskRunnable implements Runnable {
private WeakReference<Activity> activityRef;
TaskRunnable(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void run() {
Activity act = activityRef.get();
if (act != null) {
// безопасная работа с Activity
}
}
}
В этом примере анонимный Runnable захватывает неявную ссылку на Activity. Пока поток жив — Activity не может быть собран GC, даже если пользователь уже закрыл экран. Исправление WeakReference + static class разрывает эту цепочку и позволяет Activity утилизироваться.
Часто задаваемые вопросы
GC в Android (ART) — поколенческий сборщик с concurrent компактизацией, оптимизированный для мобильных устройств с ограниченной памятью. Java GC (G1, ZGC) — серверные сборщики с большими кучами и предсказуемыми паузами. ART GC не использует флаги JVM — вся настройка выполняется автоматически на уровне ОС.
Stop-The-World — момент, когда сборщик приостанавливает все потоки приложения, чтобы безопасно пройти по графу объектов или освободить память. Чем дольше STW, тем заметнее jank. ART сократил типичное время STW до 2–4 мс за счёт поколенческой архитектуры.
Используйте Android Studio Memory Profiler — он показывает рост кучи, количество аллокаций и позволяет делать Heap Dump. Для глубокого анализа применяйте LeakCanary — библиотека автоматически детектит утечки и показывает цепочку ссылок, препятствующих сборке GC.
Full GC — полная сборка всех поколений кучи, включая Old Generation. В мобильных приложениях Full GC может длиться 50–200 мс, вызывая заметный jank или ANR. Основные причины: фрагментация кучи, утечки памяти, превышение порога Old Generation.
Kotlin предоставляет корутины со структурированной конкурентностью — отмена скоупа автоматически отменяет все дочерние корутины, предотвращая утечки. Также в Kotlin есть делегат lazy для ленивой инициализации и операторы области видимости, снижающие число временных объектов.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также