La gestione automatica della memoria tramite garbage collection è un meccanismo chiave della piattaforma Android, basato sulla macchina virtuale ART. Secondo Google Android Documentation, 2026, il garbage collector libera lo sviluppatore dalla gestione manuale della memoria, rimuovendo automaticamente gli oggetti che non hanno più riferimenti. Senza GC, ogni allocazione di oggetto richiederebbe una chiamata esplicita a free o delete, cosa impossibile nell'ecosistema Java con milioni di oggetti al secondo.
Punti chiave
Garbage Collection (GC) è un processo automatico di rilevamento e liberazione della memoria occupata da oggetti che non sono più utilizzati dal programma. Nel contesto dello sviluppo mobile, GC viene utilizzato sulla piattaforma Android tramite la macchina virtuale ART, oltre che nella Java Virtual Machine standard.
A differenza dei linguaggi con gestione manuale della memoria (C, C++), dove il programmatore deve chiamare esplicitamente free o delete, GC si assume completamente il compito di tracciare il ciclo di vita degli oggetti. Lo sviluppatore crea nuovi oggetti tramite l'operatore new, mentre il collettore determina quando un oggetto diventa irraggiungibile — cioè quando non rimangono più riferimenti attivi ad esso.
Le metriche principali dell'efficienza di GC sono il tempo di pausa (pause time) e la produttività (throughput). La pausa è il periodo durante il quale l'esecuzione dell'applicazione viene fermata per effettuare la raccolta. In un ambiente mobile, pause superiori a 8–16 millisecondi sono percepibili come fotogrammi persi (jank).
Secondo Google I/O 2019, ART in Android 10 ha ridotto le pause tipiche di GC a 2–4 ms, con una riduzione del 70% rispetto a Dalvik in Android 4.4. Tuttavia, una gestione inadeguata della memoria — allocazione frequente di oggetti nei cicli, creazione di istanze temporanee non necessarie — rimane la causa principale dei problemi di prestazioni.
Tutte le implementazioni di GC in Java e Android si basano su diversi algoritmi fondamentali che vengono combinati per raggiungere un equilibrio tra tempo di pausa e completezza della pulizia. Comprendere questi algoritmi è essenziale per scrivere codice GC-friendly.
Mark-and-Sweep è l'algoritmo più semplice, che funziona in due fasi. Nella fase Mark, il collettore attraversa il grafo degli oggetti partendo dai riferimenti radice (root set) — variabili locali, campi statici, stack dei thread. Ogni oggetto raggiungibile viene marcato con un flag di vivo. Nella fase Sweep, il collettore percorre l'intero heap e libera la memoria degli oggetti non marcati.
Lo svantaggio è la frammentazione della memoria: dopo Sweep, le aree libere si alternano a quelle occupate, rendendo difficile l'allocazione di oggetti grandi. Negli scenari mobile, questo è critico poiché l'heap è tipicamente piccolo (64–512 MB su Android).
Copying Collection divide l'heap in due semispazi (semi-spaces). Gli oggetti attivi vengono copiati in modo compatto da un semispazio all'altro, senza spazi vuoti. Dopo la copia, il vecchio semispazio viene dichiarato completamente libero. L'algoritmo elimina completamente la frammentazione, ma richiede il doppio della memoria.
Negli ambienti mobile, Copying Collection viene utilizzato dai collettori generazionali per la pulizia rapida degli oggetti giovani, che statisticamente muoiono presto (ipotesi generazionale debole).
Generational Collection divide l'heap in generazioni: Young Generation (oggetti giovani) e Old Generation (oggetti vecchi sopravvissuti a diverse raccolte). La raccolta della generazione giovane (Minor GC) viene eseguita frequentemente e rapidamente, poiché la maggior parte degli oggetti muore giovane. La raccolta della generazione vecchia (Major GC o Full GC) avviene meno frequentemente ma dura più a lungo.
// Dimostrazione di GC generazionale: gli oggetti giovani muoiono velocemente
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // vive per l'intero metodo
for (Item item : items) {
Result r = new Result(item.getValue()); // muore istantaneamente
if (r.isValid()) {
process(r); // r diventa spazzatura
}
}
saveResults(results); // results passa a Old Gen
}
In questo esempio, gli oggetti Result vengono creati all'interno di un ciclo e diventano immediatamente spazzatura — sono candidati ideali per Young GC. L'oggetto results vive più a lungo e migra in Old Generation. La separazione delle generazioni consente a Minor GC di pulire gli oggetti giovani in millisecondi senza toccare l'heap vecchio.
Android si è evoluto da Dalvik VM ad ART (Android Runtime) e l'implementazione di GC è una delle differenze principali tra i due. Comprendere l'architettura di GC in Android aiuta a scrivere codice che minimizzi le pause sui dispositivi reali.
| Caratteristica | Dalvik (fino a 4.4) | ART (5.0+) |
|---|---|---|
| Tipo di GC | Mark-and-Sweep con Concurrent Mark | Generational + Concurrent |
| Pausa tipica | 10–30 ms | 2–4 ms |
| Compattazione | No (solo la frammentazione cresce) | Sì (in background, senza fermare l'app) |
| Compilazione AOT | JIT (Just-In-Time) | AOT + JIT (ibrida) |
Dalvik utilizzava una combinazione di Mark-and-Sweep con una fase concorrente. Concurrent Mark permetteva all'applicazione di continuare a funzionare durante l'attraversamento del grafo degli oggetti, ma la fase Sweep richiedeva l'arresto di tutti i thread (Stop-The-World). Su dispositivi con poca RAM (512 MB — 1 GB), le pause raggiungevano 30 ms, causando rallentamenti notevoli dell'interfaccia. Inoltre, Dalvik non compattava l'heap, quindi dopo un uso prolungato, la frammentazione aumentava e l'allocazione di oggetti grandi (ad esempio, Bitmap) poteva lanciare OutOfMemoryError anche con memoria libera totale sufficiente.
ART (Android Runtime) ha introdotto un collettore generazionale con compattazione concorrente. L'heap è diviso in tre regioni: Young, Mature (analoga a Old Generation) e Large Object Space (per oggetti più grandi di 12 KB). La raccolta della regione Young avviene in parallelo senza fermare i thread nella maggior parte dei casi. In Android 10+, è stato introdotto Concurrent Copying — la compattazione viene eseguita in un thread di background senza Stop-The-World.
Grazie all'architettura di ART, le pause tipiche di GC sono state ridotte a 2–4 ms e negli scenari con predominanza di oggetti giovani — a 0.5–1 ms. Ciò ha permesso ai dispositivi Android di mantenere 60 FPS stabili anche durante operazioni di memoria attive.
Nell'ecosistema Java, esistono diverse implementazioni di GC, ciascuna con il proprio profilo prestazionale. Per lo sviluppo Android, la scelta è limitata ad ART, ma la conoscenza di Java GC è utile quando si scrive codice lato server per applicazioni mobile e quando si sviluppa con Kotlin Multiplatform.
Serial GC è un collettore a thread singolo con arresto completo dell'applicazione (Stop-The-World). Ogni operazione Mark, Sweep e Compact viene eseguita da un thread. Le prestazioni sono basse — non viene utilizzato per server mobile. Adatto solo per applicazioni piccole con heap fino a 100 MB.
Parallel GC (noto anche come Throughput Collector) utilizza più thread per tutte le fasi di raccolta. È orientato alla massima produttività (throughput) — minimizzando il tempo speso in GC rispetto al tempo di esecuzione dell'applicazione. Attivato tramite il flag -XX:+UseParallelGC nella JVM.
G1 (Garbage-First) GC è il collettore predefinito in Java 9+. L'heap è diviso in regioni di 1–32 MB. G1 prevede il tempo di pausa e cerca di rimanere entro un limite specificato (default 200 ms). Priorità: le regioni con la maggiore quantità di spazzatura vengono pulite per prime (da qui il nome). G1 è efficace per server con heap grandi (4–64 GB) con pause prevedibili.
// Abilitazione di G1 GC con pausa target di 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("Heap usage exceeded threshold: " + used);
System.out.println("Consider reducing allocations");
}
}
}
Monitorare l'heap tramite Runtime consente di rilevare le perdite di memoria in fase iniziale. Se used supera l'80% dell'heap massimo in condizioni di funzionamento stabile — questo è un segnale di possibile perdita o di consumo eccessivo di memoria da parte dell'applicazione.
Anche il moderno ART GC non risolve tutti i problemi — l'uso improprio della memoria rimane la causa principale di jank e ANR (Application Not Responding). Esaminiamo gli scenari principali e i metodi di ottimizzazione.
Pause GC — arresti dei thread dell'applicazione durante la raccolta. Sullo schermo, questo si manifesta come fotogrammi persi, quando il tempo tra due fotogrammi supera 16.6 ms (60 FPS). Se GC dura 30 ms, viene disegnato un solo fotogramma invece di due — l'utente vede balbettii dell'interfaccia.
Le cause principali delle pause lunghe: un gran numero di oggetti vivi in Old Generation, frammentazione dell'heap, Full GC frequenti. Per la diagnostica si utilizzano Android Studio Profiler e systrace.
La regola principale del codice GC-friendly è minimizzare il numero di oggetti allocati. Ogni nuovo oggetto richiede non solo l'allocazione di memoria ma anche la successiva raccolta. Anche se GC è veloce, 1000 allocazioni extra al secondo generano 1000 controlli per il collettore.
Una perdita di memoria si verifica quando un oggetto rimane raggiungibile anche se non è più necessario. GC non può eliminare tale oggetto e la memoria si esaurisce gradualmente. Cause tipiche: listener non deregistrati, riferimenti statici ad Activity, classi anonime che catturano il contesto esterno e Cursor/InputStream non chiusi.
// Perdita di memoria: classe anonima trattiene riferimento ad Activity
public void startTask() {
new Thread(new Runnable() { // trattiene implicitamente this (Activity)
@Override
public void run() {
// operazione lunga...
System.out.println("Done");
}
}).start();
}
// Correzione: classe statica annidata + 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) {
// lavoro sicuro con Activity
}
}
}
In questo esempio, il Runnable anonimo cattura un riferimento implicito all'Activity. Finchè il thread è vivo — Activity non può essere raccolta da GC, anche se l'utente ha già chiuso lo schermo. La correzione con WeakReference + classe statica rompe questa catena e consente ad Activity di essere liberata.
Domande frequenti
GC in Android (ART) è un collettore generazionale con compattazione concorrente, ottimizzato per dispositivi mobile con memoria limitata. Java GC (G1, ZGC) sono collettori lato server con heap grandi e pause prevedibili. ART GC non utilizza flag JVM — tutta la regolazione viene eseguita automaticamente a livello di OS.
Stop-The-World è il momento in cui il collettore mette in pausa tutti i thread dell'applicazione per attraversare in sicurezza il grafo degli oggetti o liberare memoria. Più lungo è lo STW, più evidente è il jank. ART ha ridotto il tempo tipico di STW a 2–4 ms grazie alla sua architettura generazionale.
Usa Android Studio Memory Profiler — mostra la crescita dell'heap, il numero di allocazioni e permette di fare Heap Dump. Per un'analisi approfondita, usa LeakCanary — la libreria rileva automaticamente le perdite e mostra la catena di riferimenti che impedisce la raccolta GC.
Full GC è una raccolta completa di tutte le generazioni dell'heap, inclusa Old Generation. Nelle applicazioni mobile, Full GC può durare 50–200 ms, causando jank o ANR evidenti. Cause principali: frammentazione dell'heap, perdite di memoria, superamento della soglia di Old Generation.
Kotlin fornisce coroutine con concorrenza strutturata — la cancellazione dell'ambito annulla automaticamente tutte le coroutine figlie, prevenendo perdite. Kotlin ha anche il delegato lazy per l'inizializzazione pigra e funzioni di ambito che riducono il numero di oggetti temporanei.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche