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 — смањење алокација, коришћење пулова објеката и правилан одабир Collection типова смањују оптерећење сакупљача

Шта је 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 ms, што је 70% мање у поређењу са Dalvik-ом у Android 4.4. Ипак, неправилан рад са меморијом — честа алокација објеката у петљама, креирање привремених инстанци без потребе — остаје главни узрок проблема са перформансама.

Како ради сакупљач отпада: основни алгоритми

Све GC имплементације у Java и Android-у заснивају се на неколико фундаменталних алгоритама који се комбинују да би се постигао баланс између времена паузе и потпуности чишћења. Разумевање ових алгоритама је потребно за писање GC-friendly кода.

Mark-and-Sweep

Mark-and-Sweep — најједноставнији алгоритам који ради у две фазе. У фази Mark, сакупљач обилази граф објеката, почевши од коренских референци (root set) — локалних промењивих, статичких поља, стекова нити. Сваки доступан објекат се означава ознаком live. У фази Sweep, сакупљач пролази кроз цеокупно гомиле и ослабађа меморију неозначених објеката.

Недостатак је фрагментација меморије: након Sweep-а, слободни делови се изменују са заузетима, што отежава додељивање великих објеката. У мобилним сценаријима ово је критично јер је гомиле обично мало (64–512 MB на Android-у).

Copying Collection

Copying Collection дели гомиле на два полупростора (semi-spaces). Активни објекти се копирају из једног полупростора у други компактно, без прекида. Након копирања, стари полупростор се проглашава потпуно слободним. Алгоритам потпуно елиминише фрагментацију, али захтева два пута више меморије.

У мобилним окружењима, Copying Collection користе генерацијски сакупљачи за брзо чишћење младих објеката, који статистички рано умиру (u0438потеза слабе генерације).

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 MarkГенерацијски + Concurrent
Типична пауза10–30 ms2–4 ms
КомпактизацијаНе (само фрагментација расте)Да (u0443 позадини, без заустављања апликације)
AOT компилацијаJIT (Just-In-Time)AOT + JIT (хибрид)

Dalvik GC

Dalvik је користио комбинацију Mark-and-Sweep са concurrent фазом. Concurrent Mark је омогућавао апликацији да настави рад током обиласка графа објеката, али је фаза Sweep захтевала заустављање свих нити (Stop-The-World). На уређајима са мало RAM (512 MB — 1 GB), паузе су досизале 30 ms, што је изазивало приметно успоравање интерфејса. Поред тога, Dalvik није компактизовао гомилу, па је након дужег рада фрагментација расла, а додељивање великих објеката (нпр. Bitmap) је могло да изазове OutOfMemoryError чак и уз довољну укупну слободну меморију.

ART GC

ART (Android Runtime) је увела генерацијског сакупљача са concurrent компактизацијом. Гомиле су подељене на три региона: Young, Mature (аналог Old Generation) и Large Object Space (за објекте веће од 12 KB). Сакупљање Young региона се обавља паралелно без заустављања нити у већини случајева. У Android 10+ појавила се Concurrent Copying — компактизација се обавља у позадинској нити без Stop-The-World.

Захваљујући архитектури ART, типичне GC паузе су сведене на 2–4 ms, а у сценаријима са превлађу младих објеката — на 0.5–1 ms. Ово је омогућило Android уређајима да обезбеде стабилних 60 FPS чак и при активном раду са меморијом.

Врсте сакупљача отпада у Java-и

У Java екосистему постоји неколико GC имплементација, свака са сопственим профилом перформанси. За Android развој, избор је ограничен на ART, али је познавање Java GC корисно при писању серверског дела мобилних апликација и при развоју са Kotlin Multiplatform.

Serial GC

Serial GC — једнонитни сакупљач са потпуним заустављањем апликације (Stop-The-World). Свака операција Mark, Sweep и Compact се извршава једном нити. Перформансе је ниски — није примењив за мобилне сервере. Погодан само за мале апликације са гомилом до 100 MB.

Parallel GC

Parallel GC (такође познат као Throughput Collector) користи више нити за све фазе сакупљања. Усмерен је ка максималном протоку (throughput) — минимизира време потрошено на GC у односу на време рада апликације. Укључује се преко заставице -XX:+UseParallelGC у JVM-у.

G1 GC

G1 (Garbage-First) GC — подразумевани сакупљач у Java 9+. Гомиле су подељене на регионе од 1–32 MB. G1 предвиђа време паузе и тежи да се уклопи у задати лимит (подразумевано 200 ms). Приоритет: прво се чисте региони са највећим обимом отпада (отатле и назив). G1 је ефикасан за сервере са великом гомилом (4–64 GB) и предвидивим паузама.

java
// Укључивање G1 GC са циљном паузом од 100 ms
// 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("Искоришћеност гомиле је премашила праг: " + used);
            System.out.println("Размотрите смањење алокација");
        }
    }
}

Праћење гомиле кроз Runtime омогућава рано откривање цурења меморије. Ако used прелази 80% максималне гомиле при стабилном раду — то је сигнал за могуће цурење или прекомерну потрошњу меморије од стране апликације.

Проблеми GC и оптимизација меморије у мобилним апликацијама

Чак и савремени ART GC не решава све проблеме — неправилно коришћење меморије остаје главни узрок jank-а и ANR (Application Not Responding). Размотримо основне сценарије и методе оптимизације.

GC Pauses и Jank

GC Pauses — заустављања нити апликације током сакупљања. На екрану се манифестује како пропуштени оквири када време између два оквира прелази 16.6 ms (60 FPS). Ако GC траје 30 ms, црта се само један оквир уместо два — корисник види трзаје интерфејса.

Главни узроци дугих пауза: велики број живих објеката у 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("Готово");
        }
    }).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-у разликује од GC-а у Java-и?

GC у Android-у (ART) је генерацијски сакупљач са concurrent компактизацијом, оптимизован за мобилне уређаје са ограниченом меморијом. Java GC (G1, ZGC) су серверски сакупљачи са великим гомилама и предвидивим паузама. ART GC не користи JVM заставице — целокупно подешавање се обавља аутоматски на нивоу оперативног система.

Шта је Stop-The-World у GC-у?

Stop-The-World — тренутак када сакупљач зауставља све нити апликације да би безбедно прошао кроз граф објеката или ослободио меморију. Што је STW дужи, то је jank уочљивији. ART је скратио типично STW време на 2–4 ms захваљујући генерацијској архитектури.

Како открити цурење меморије у Android-у?

Користите Android Studio Memory Profiler — приказује раст гомиле, број алокација и омогућава Heap Dump. За дубоку анализу користите LeakCanary — библиотека аутоматски открива цурења и приказује ланац референци који спречавају GC сакупљање.

Када се јавља Full GC и зашто је опасан?

Full GC — потпуно сакупљање свих генерација гомиле, укључујући Old Generation. У мобилним апликацијама Full GC може да траје 50–200 ms, изазивајући приметан jank или ANR. Главни узроци: фрагментација гомиле, цурење меморије, прекорачење прага Old Generation-а.

Како Kotlin помаже у избегавању цурења меморије?

Kotlin пружа корутине са структурираном конкурентношћу — отказивање scope-а аутоматски отказује све подређене корутине, спречавајући цурења. Такође, у Kotlin-у постоји делегат lazy за лењиву иницијализацију и оператори области видљивости који смањују број привремених објеката.

Истаци

  • Garbage Collection — аутоматско управљање меморијом уклањањем недоступних објеката, основа Android Runtime-а
  • Mark-and-Sweep — основни алгоритам са двофазним сакупљањем, пати од фрагментације гомиле
  • Copying Collection — отклања фрагментацију копирањем живих објеката у компактни полупростор
  • Generational GC — дели гомилу на генерације (Young/Old), убрзавајући сакупљање младих кратковечних објеката
  • ART у Android-у — генерацијски сакупљач са concurrent компактизацијом и паузама 2–4 ms, који је заменио Dalvik у Android 5.0
  • Оптимизација GC — смањење алокација, пулови објеката, примитиви уместо омотача и SparseArray уместо HashMap-а смањују оптерећење сакупљача
  • Дијагностика — Android Studio Profiler, systrace и LeakCanary су основни алати за откривање проблема са меморијом

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође