Автоматичне керування пам'яттю через збирання сміття — ключовий механізм платформи 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 Mark дозволяв застосунку продовжувати роботу під час обходу графа об'єктів, але етап Sweep вимагав зупинки всіх потоків (Stop-The-World). На пристроях із малим об'ємом RAM (512 МБ — 1 ГБ) паузи досягали 30 мс, що викликало помітні підгальмовування інтерфейсу. Крім того, Dalvik не компактизував купу, тому після тривалої роботи зростала фрагментація, і виділення великих об'єктів (наприклад, Bitmap) могло викинути OutOfMemoryError при достатньому загальному об'ємі вільної пам'яті.
ART (Android Runtime) впровадив поколіннєвий збирач із конкурентною компактизацією. Купа ділиться на три регіони: 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 (також відомий як 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 + статичний клас розриває цей ланцюжок і дозволяє Activity утилізуватися.
Часто задавані питання
GC в Android (ART) — поколіннєвий збирач із конкурентною компактизацією, оптимізований для мобільних пристроїв з обмеженою пам'яттю. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також