Gestiunea automată a memoriei prin colectarea gunoiului este mecanismul cheie al platformei Android, bazat pe mașina virtuală ART. Conform Google Android Documentation, 2026, colectorul de gunoi eliberează dezvoltatorul de gestionarea manuală a memoriei, ștergând automat obiectele care nu mai au referințe. Fără GC, fiecare alocare de obiect ar necesita apelarea explicită a free sau delete, ceea ce în ecosistemul Java cu milioane de obiecte pe secundă este fizic imposibil.
Principalele puncte
Garbage Collection (GC) — este un proces automat de detectare și eliberare a memoriei ocupate de obiecte care nu mai sunt utilizate de program. În contextul dezvoltării mobile, GC este aplicat pe platforma Android prin mașina virtuală ART, precum și în mașina virtuală Java standard.
Spre deosebire de limbajele cu gestionare manuală a memoriei (C, C++), unde programatorul trebuie să apeleze explicit free sau delete, GC preia complet sarcina de urmărire a ciclului de viață al obiectelor. Dezvoltatorul creează obiecte noi prin operatorul new, iar colectorul determină momentul când un obiect devine inaccesibil — adică nu mai are nicio referință activă.
Principalii parametri ai eficienței GC — timpul de pauză (pause time) și capacitatea de transfer (throughput). Pauza este intervalul în care execuția aplicației este suspendată pentru efectuarea colectării. În mediul mobil, pauzele mai lungi de 8–16 milisecunde sunt vizibile ca cadre pierdute (jank).
Conform datelor Google I/O 2019, ART în Android 10 a redus pauzele tipice GC la 2–4 ms, ceea ce este cu 70% mai puțin comparativ cu Dalvik în Android 4.4. Cu toate acestea, gestionarea incorectă a memoriei — alocarea frecventă de obiecte în bucle, crearea de instanțe temporare inutile — rămâne principala cauză a problemelor de performanță.
Toate implementările GC în Java și Android se bazează pe câțiva algoritmi fundamentali, care sunt combinați pentru a atinge un echilibru între timpul de pauză și completitudinea curățării. Înțelegerea acestor algoritmi este necesară pentru a scrie cod compatibil cu GC.
Mark-and-Sweep — cel mai simplu algoritm, care funcționează în două etape. În faza Mark, colectorul parcurge graful de obiecte, începând de la referințele rădăcină (root set) — variabile locale, câmpuri statice, stivele firelor de execuție. Fiecare obiect accesibil este marcat cu un flag live. În faza Sweep, colectorul parcurge întregul heap și eliberează memoria obiectelor nemarcate.
Dezavantajul — fragmentarea memoriei: după Sweep, zonele libere alternează cu cele ocupate, ceea ce îngreunează alocarea obiectelor mari. În scenariile mobile, acest lucru este critic, deoarece heap-ul este de obicei mic (64–512 MB pe Android).
Copying Collection împarte heap-ul în două semi-spații (semi-spaces). Obiectele active sunt copiate dintr-un semi-spațiu în celălalt compact, fără întreruperi. După copiere, vechiul semi-spațiu este declarat întreg liber. Algoritmul elimină complet fragmentarea, dar necesită de două ori mai multă memorie.
În mediile mobile, Copying Collection este utilizat de colectoarele generaționale pentru curățarea rapidă a obiectelor tinere, care statistic mor devreme (ipoteza generației slabe).
Generational Collection împarte heap-ul în generații: Young Generation (obiecte tinere) și Old Generation (obiecte vechi care au supraviețuit mai multor colectări). Colectarea generației tinere (Minor GC) se efectuează frecvent și rapid, deoarece majoritatea obiectelor mor tinere. Colectarea generației vechi (Major GC sau Full GC) are loc mai rar, dar durează mai mult.
// Demonstrație GC generațional: obiectele tinere mor rapid
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // trăiește toată metoda
for (Item item : items) {
Result r = new Result(item.getValue()); // moare instantaneu
if (r.isValid()) {
process(r); // r devine gunoi
}
}
saveResults(results); // results trece în Old Gen
}
În acest exemplu, obiectele Result sunt create în interiorul buclei și devin imediat gunoi — sunt candidați ideali pentru Young GC. Obiectul results trăiește mai mult și migrează în Old Generation. Separarea generațiilor permite Minor GC să curețe obiectele tinere în milisecunde, fără a atinge heap-ul vechi.
Android a parcurs drumul de la Dalvik VM la ART (Android Runtime), iar implementarea GC este una dintre diferențele cheie dintre ele. Înțelegerea arhitecturii GC în Android ajută la scrierea unui cod care minimizează pauzele pe dispozitive reale.
| Caracteristică | Dalvik (până la 4.4) | ART (5.0+) |
|---|---|---|
| Tip GC | Mark-and-Sweep cu Concurrent Mark | Generațional + Concurrent |
| Pauză tipică | 10–30 ms | 2–4 ms |
| Compactare | Nu (doar fragmentarea crește) | Da (în fundal, fără oprirea aplicației) |
| Compilare AOT | JIT (Just-In-Time) | AOT + JIT (hibrid) |
Dalvik folosea o combinație de Mark-and-Sweep cu o fază concurrent. Concurrent Mark permitea aplicației să continue lucrul în timpul parcurgerii grafului de obiecte, dar faza Sweep necesita oprirea tuturor firelor (Stop-The-World). Pe dispozitivele cu puțină RAM (512 MB — 1 GB), pauzele ajungeau la 30 ms, ceea ce cauza încetiniri vizibile ale interfeței. În plus, Dalvik nu compacta heap-ul, așa că după o funcționare prelungită fragmentarea creștea, iar alocarea de obiecte mari (de exemplu, Bitmap) putea arunca OutOfMemoryError chiar și cu suficientă memorie liberă totală.
ART (Android Runtime) a introdus un colector generațional cu compactare concurrentă. Heap-ul este împărțit în trei regiuni: Young, Mature (analog Old Generation) și Large Object Space (pentru obiecte mai mari de 12 KB). Colectarea regiunii Young are loc în paralel, fără oprirea firelor în majoritatea cazurilor. În Android 10+ a apărut Concurrent Copying — compactarea se efectuează în firul de fundal fără Stop-The-World.
Datorită arhitecturii ART, pauzele tipice GC s-au redus la 2–4 ms, iar în scenariile cu predominanță de obiecte tinere — la 0.5–1 ms. Acest lucru a permis dispozitivelor Android să asigure 60 FPS stabile chiar și în condiții de lucru activ cu memoria.
În ecosistemul Java există mai multe implementări GC, fiecare cu propriul profil de performanță. Pentru dezvoltarea Android, alegerea este limitată la ART, dar cunoașterea Java GC este utilă la scrierea părții de server a aplicațiilor mobile și la dezvoltarea cu Kotlin Multiplatform.
Serial GC — colector cu un singur fir de execuție și oprire completă a aplicației (Stop-The-World). Fiecare operație Mark, Sweep și Compact este executată de un singur fir. Performanța este scăzută — nu se aplică serverelor mobile. Este potrivit doar pentru aplicații mici cu heap de până la 100 MB.
Parallel GC (cunoscut și ca Throughput Collector) utilizează mai multe fire pentru toate fazele de colectare. Este orientat spre capacitatea maximă de transfer (throughput) — minimizează timpul petrecut în GC relativ la timpul de funcționare a aplicației. Se activează prin flagul -XX:+UseParallelGC în JVM.
G1 (Garbage-First) GC — colectorul implicit în Java 9+. Heap-ul este împărțit în regiuni de 1–32 MB. G1 prognozează timpul de pauză și încearcă să se încadreze în limita stabilită (implicit 200 ms). Prioritate: mai întâi sunt curățate regiunile cu cel mai mare volum de gunoi (de unde și numele). G1 este eficient pentru servere cu heap mare (4–64 GB) și pauze previzibile.
// Activarea G1 GC cu pauză țintă de 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("Pragul de utilizare a heap-ului a fost depășit: " + used);
System.out.println("Luați în considerare reducerea alocărilor");
}
}
}
Monitorizarea heap-ului prin Runtime permite detectarea timpurie a scurgerilor de memorie. Dacă used depășește 80% din heap-ul maxim în condiții de funcționare stabilă — acesta este un semnal de posibilă scurgere sau consum excesiv de memorie de către aplicație.
Chiar și ART GC modern nu rezolvă toate problemele — utilizarea incorectă a memoriei rămâne principala cauză a jank și ANR (Application Not Responding). Să analizăm principalele scenarii și metode de optimizare.
GC Pauses — opriri ale firelor aplicației în timpul colectării. Pe ecran, aceasta se manifestă ca cadre pierdute, când timpul dintre două cadre depășește 16.6 ms (60 FPS). Dacă GC durează 30 ms, se desenează un singur cadru în loc de două — utilizatorul vede încetiniri ale interfeței.
Cauzele principale ale pauzelor lungi: numărul mare de obiecte vii în Old Generation, fragmentarea heap-ului, Full GC frecvente. Pentru diagnosticare se folosesc Android Studio Profiler și systrace.
Regula principală a codului compatibil cu GC — minimizarea numărului de obiecte alocate. Fiecare obiect nou necesită nu doar alocarea memoriei, ci și colectarea ulterioară. Chiar dacă GC este rapid, 1000 de alocări inutile pe secundă generează 1000 de verificări pentru colector.
Scurgerea de memorie apare atunci când un obiect rămâne accesibil, deși nu mai este necesar. GC nu poate șterge un astfel de obiect, iar memoria se epuizează treptat. Cauze tipice: ascultători nedezabonați, referințe statice la Activity, clase anonime care captează contextul extern și Cursor/InputStream neînchise.
// Scurgere de memorie: clasa anonimă păstrează o referință la Activity
public void startTask() {
new Thread(new Runnable() { // păstrează implicit this (Activity)
@Override
public void run() {
// operație lungă...
System.out.println("Gata");
}
}).start();
}
// Corecție: clasă statică imbricată + 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) {
// lucru sigur cu Activity
}
}
}
În acest exemplu, Runnable-ul anonim capturează o referință implicită la Activity. Cât timp firul este viu — Activity nu poate fi colectat de GC, chiar dacă utilizatorul a închis deja ecranul. Corecția WeakReference + static class întrerupe acest lanț și permite utilizarea Activity.
Întrebări frecvente
GC în Android (ART) este un colector generațional cu compactare concurrentă, optimizat pentru dispozitive mobile cu memorie limitată. Java GC (G1, ZGC) sunt colectoare server cu heap-uri mari și pauze previzibile. ART GC nu folosește flaguri JVM — toată configurarea se face automat la nivelul sistemului de operare.
Stop-The-World — momentul în care colectorul oprește toate firele aplicației pentru a parcurge în siguranță graful de obiecte sau a elibera memoria. Cu cât STW este mai lung, cu atât jank este mai vizibil. ART a redus timpul tipic STW la 2–4 ms datorită arhitecturii generaționale.
Folosiți Android Studio Memory Profiler — arată creșterea heap-ului, numărul de alocări și permite efectuarea unui Heap Dump. Pentru analiză aprofundată, utilizați LeakCanary — biblioteca detectează automat scurgerile și arată lanțul de referințe care împiedică colectarea GC.
Full GC — colectarea completă a tuturor generațiilor heap-ului, inclusiv Old Generation. În aplicațiile mobile, Full GC poate dura 50–200 ms, provocând jank vizibil sau ANR. Cauze principale: fragmentarea heap-ului, scurgeri de memorie, depășirea pragului Old Generation.
Kotlin oferă corutini cu concurență structurată — anularea scope-ului anulează automat toate corutinele copil, prevenind scurgerile. De asemenea, în Kotlin există delegatul lazy pentru inițializare întârziată și operatorii de domeniu de vizibilitate care reduc numărul de obiecte temporare.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și