Garbage Collection (GC): що це, алгоритми та збирання сміття в мобільній розробці

Автор: IT Sectr Опубліковано: 2026-03-29 Час читання: 10 хв

Автоматичне керування пам'яттю через збирання сміття — ключовий механізм платформи Android, заснований на віртуальній машині ART. За даними Google Android Documentation, 2026, збирач сміття звільняє розробника від ручного керування пам'яттю, автоматично видаляючи об'єкти, на які більше немає посилань. Без GC кожне виділення об'єкта вимагало б явного виклику free або delete, що в Java-екосистемі з мільйонами об'єктів на секунду неможливо фізично.

Головне

  • Garbage Collection — автоматичний механізм звільнення пам'яті шляхом видалення невикористовуваних об'єктів у Java та Android
  • Базові алгоритми — Mark-and-Sweep, Copying Collection та Generational Collection визначають ефективність збирання
  • ART і Dalvik — дві реалізації віртуальної машини Android, де ART (Android Runtime) замінила Dalvik починаючи з Android 5.0
  • GC Pauses — зупинки виконання застосунку під час збирання — головна причина jank та проблем із продуктивністю
  • Оптимізація GC — зменшення алокацій, використання пулів об'єктів та правильний вибір типів колекцій знижують навантаження на збирач

Що таке Garbage Collection (GC)?

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-and-Sweep — найпростіший алгоритм, що працює у два етапи. На етапі Mark збирач обходить граф об'єктів, починаючи з кореневих посилань (root set) — локальних змінних, статичних полів, стеків потоків. Кожен досяжний об'єкт позначається прапорцем live. На етапі Sweep збирач проходить по всій купі та звільняє пам'ять непозначених об'єктів.

Недолік — фрагментація пам'яті: після Sweep вільні ділянки чергуються із зайнятими, що ускладнює виділення великих об'єктів. У мобільних сценаріях це критично, оскільки купа зазвичай мала (64–512 МБ на Android).

Copying Collection

Copying Collection ділить купу на два півпростори (semi-spaces). Активні об'єкти копіюються з одного півпростору в інший компактно, без розривів. Після копіювання старий півпростір повністю оголошується вільним. Алгоритм повністю усуває фрагментацію, але потребує вдвічі більше пам'яті.

У мобільних середовищах Copying Collection застосовується поколіннєвими збирачами для швидкого очищення молодих об'єктів, які статистично вмирають рано (гіпотеза слабкого покоління).

Generational Collection

Generational Collection ділить купу на покоління: Young Generation (молоді об'єкти) та Old Generation (старі, що пережили кілька збирань). Збирання молодого покоління (Minor GC) виконується часто та швидко, оскільки більшість об'єктів вмирають молодими. Збирання старого покоління (Major GC або Full GC) відбувається рідше, але триває довше.

java
// Демонстрація поколіннєвого 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 очищати молоді об'єкти за мілісекунди, не торкаючись старої купи.

Garbage Collection в Android: ART і Dalvik

Android пройшов шлях від Dalvik VM до ART (Android Runtime), і реалізація GC — одна з ключових відмінностей між ними. Розуміння архітектури GC в Android допомагає писати код, який мінімізує паузи на реальних пристроях.

ХарактеристикаDalvik (до 4.4)ART (5.0+)
Тип GCMark-and-Sweep з Concurrent MarkGenerational + Concurrent
Типова пауза10–30 мс2–4 мс
КомпактизаціяНемає (тільки фрагментація зростає)Є (у фоні, без зупинки застосунку)
AOT-компіляціяJIT (Just-In-Time)AOT + JIT (гібрид)

Dalvik GC

Dalvik використовував комбінацію Mark-and-Sweep із конкурентною фазою. Concurrent Mark дозволяв застосунку продовжувати роботу під час обходу графа об'єктів, але етап Sweep вимагав зупинки всіх потоків (Stop-The-World). На пристроях із малим об'ємом RAM (512 МБ — 1 ГБ) паузи досягали 30 мс, що викликало помітні підгальмовування інтерфейсу. Крім того, Dalvik не компактизував купу, тому після тривалої роботи зростала фрагментація, і виділення великих об'єктів (наприклад, Bitmap) могло викинути OutOfMemoryError при достатньому загальному об'ємі вільної пам'яті.

ART GC

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

В екосистемі Java існує кілька реалізацій GC, кожна зі своїм профілем продуктивності. Для Android-розробки вибір обмежений ART, але знання Java GC корисне при написанні серверної частини мобільних застосунків та при розробці на Kotlin Multiplatform.

Serial GC

Serial GC — однопотоковий збирач із повною зупинкою застосунку (Stop-The-World). Кожна операція Mark, Sweep та Compact виконується одним потоком. Продуктивність низька — для мобільних серверів не застосовується. Підходить тільки для невеликих застосунків із купою до 100 МБ.

Parallel GC

Parallel GC (також відомий як Throughput Collector) використовує кілька потоків для всіх фаз збирання. Орієнтований на максимальну пропускну здатність (throughput) — мінімізує час, що витрачається на GC відносно часу роботи застосунку. Вмикається через прапорець -XX:+UseParallelGC в JVM.

G1 GC

G1 (Garbage-First) GC — типовий збирач у Java 9+. Купа ділиться на регіони по 1–32 МБ. G1 прогнозує час паузи та прагне вкластися в заданий ліміт (за замовчуванням 200 мс). Priority: спочатку очищаються регіони з найбільшим об'ємом сміття (звідси назва). G1 ефективний для серверів з великою купою (4–64 ГБ) і передбачуваними паузами.

java
// Включення 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% від максимальної купи при стабільній роботі — це сигнал про можливий витік або надмірне споживання пам'яті застосунком.

Проблеми GC та оптимізація пам'яті в мобільних застосунках

Навіть сучасний ART GC не вирішує всіх проблем — неправильне використання пам'яті залишається головною причиною jank та ANR (Application Not Responding). Розглянемо основні сценарії та методи оптимізації.

GC Pauses та Jank

GC Pauses — зупинки потоків застосунку на час збирання. На екрані це проявляється як пропущені кадри, коли час між двома кадрами перевищує 16.6 мс (60 FPS). Якщо GC триває 30 мс, малюється тільки один кадр замість двох — користувач бачить смикання інтерфейсу.

Основні причини довгих пауз: велика кількість живих об'єктів в Old Generation, фрагментація купи, часті Full GC. Для діагностики використовуються Android Studio Profiler та systrace.

Зниження навантаження на GC

Головне правило GC-friendly коду — мінімізувати кількість об'єктів, що алокуються. Кожен новий об'єкт потребує не тільки виділення пам'яті, але й подальшого збирання. Навіть якщо GC швидкий, 1000 зайвих алокацій на секунду дають 1000 перевірок збирачеві.

  • Уникайте створення об'єктів у циклах — виносьте створення за межі циклу, використовуйте локальні змінні повторно
  • Використовуйте пули об'єктів — для Bitmap, byte[] та інших важких структур застосовуйте Object Pool або RecyclerView.ViewHolder
  • Надавайте перевагу примітивам — int замість Integer, float замість Float уникають автопакування (autoboxing)
  • Використовуйте SparseArray — замість HashMap<Integer, V> в Android SDK є SparseArray, LongSparseArray, які працюють із примітивами
  • StringBuilder замість конкатенації — кожне додавання рядків створює новий об'єкт String

Витоки пам'яті

Витік пам'яті виникає, коли об'єкт залишається досяжним, хоча більше не потрібен. GC не може видалити такий об'єкт, і пам'ять поступово вичерпується. Типові причини: не відписані слухачі, статичні посилання на Activity, анонімні класи, що захоплюють зовнішній контекст, та незакриті Cursor/InputStream.

java
// Витік пам'яті: анонімний клас тримає посилання на 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 відрізняється від GC в Java?

GC в Android (ART) — поколіннєвий збирач із конкурентною компактизацією, оптимізований для мобільних пристроїв з обмеженою пам'яттю. Java GC (G1, ZGC) — серверні збирачі з великими купами та передбачуваними паузами. ART GC не використовує прапорці JVM — все налаштування виконується автоматично на рівні ОС.

Що таке Stop-The-World в GC?

Stop-The-World — момент, коли збирач призупиняє всі потоки застосунку, щоб безпечно пройти по графу об'єктів або звільнити пам'ять. Чим довше STW, тим помітніший jank. ART скоротив типовий час STW до 2–4 мс за рахунок поколіннєвої архітектури.

Як виявити витік пам'яті в Android?

Використовуйте Android Studio Memory Profiler — він показує зростання купи, кількість алокацій та дозволяє робити Heap Dump. Для глибокого аналізу застосовуйте LeakCanary — бібліотека автоматично виявляє витоки та показує ланцюжок посилань, що перешкоджають збиранню GC.

Коли відбувається Full GC і чим він небезпечний?

Full GC — повне збирання всіх поколінь купи, включаючи Old Generation. У мобільних застосунках Full GC може тривати 50–200 мс, викликаючи помітний jank або ANR. Основні причини: фрагментація купи, витоки пам'яті, перевищення порогу Old Generation.

Як Kotlin допомагає уникнути витоків пам'яті?

Kotlin надає корутини зі структурованою конкурентністю — скасування скоупу автоматично скасовує всі дочірні корутини, запобігаючи витокам. Також у Kotlin є делегат lazy для лінивої ініціалізації та оператори області видимості, що знижують кількість тимчасових об'єктів.

Підсумки

  • Garbage Collection — автоматичне керування пам'яттю через видалення недосяжних об'єктів, основа Android Runtime
  • Mark-and-Sweep — базовий алгоритм із двофазним збиранням, страждає від фрагментації купи
  • Copying Collection — усуває фрагментацію за рахунок копіювання живих об'єктів у компактний півпростір
  • Generational GC — розділяє купу на покоління (Young/Old), прискорюючи збирання молодих короткоживучих об'єктів
  • ART в Android — поколіннєвий збирач із конкурентною компактизацією та паузами 2–4 мс, що змінив Dalvik в Android 5.0
  • Оптимізація GC — зменшення алокацій, пули об'єктів, примітиви замість обгорток та SparseArray замість HashMap знижують навантаження на збирач
  • Діагностика — Android Studio Profiler, systrace та LeakCanary — основні інструменти для виявлення проблем із пам'яттю

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також