Garbage Collection (GC): cos'è, algoritmi e garbage collection nello sviluppo mobile

Autore: IT Sectr Pubblicato: 2026-03-29 Tempo di lettura: 10 min

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 — meccanismo automatico di liberazione della memoria rimuovendo oggetti inutilizzati in Java e Android
  • Algoritmi di base — Mark-and-Sweep, Copying Collection e Generational Collection determinano l'efficienza della raccolta
  • ART e Dalvik — due implementazioni della macchina virtuale Android, dove ART (Android Runtime) ha sostituito Dalvik a partire da Android 5.0
  • Pause GC — arresti dell'esecuzione dell'applicazione durante la raccolta — causa principale di jank e problemi di prestazioni
  • Ottimizzazione GC — riduzione delle allocazioni, uso di pool di oggetti e corretta scelta dei tipi di raccolta riducono il carico del collettore

Cos'è Garbage Collection (GC)?

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.

Come funziona il garbage collector: algoritmi di base

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

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

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

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.

java
// 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.

Garbage Collection in Android: ART e Dalvik

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.

CaratteristicaDalvik (fino a 4.4)ART (5.0+)
Tipo di GCMark-and-Sweep con Concurrent MarkGenerational + Concurrent
Pausa tipica10–30 ms2–4 ms
CompattazioneNo (solo la frammentazione cresce)Sì (in background, senza fermare l'app)
Compilazione AOTJIT (Just-In-Time)AOT + JIT (ibrida)

Dalvik GC

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 GC

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.

Tipi di garbage collector in Java

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

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

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 GC

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.

java
// 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.

Problemi GC e ottimizzazione della memoria nelle applicazioni mobile

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 e Jank

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.

Riduzione del carico GC

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.

  • Evita di creare oggetti nei cicli — sposta la creazione fuori dal ciclo, riutilizza le variabili locali
  • Usa pool di oggetti — per Bitmap, byte[] e altre strutture pesanti, usa Object Pool o RecyclerView.ViewHolder
  • Preferisci i primitivi — int invece di Integer, float invece di Float evitano l'autoboxing
  • Usa SparseArray — invece di HashMap<Integer, V>, Android SDK offre SparseArray, LongSparseArray che lavorano con primitivi
  • StringBuilder invece di concatenazione — ogni somma di stringhe crea un nuovo oggetto String

Perdite di memoria

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.

java
// 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

In che modo GC in Android differisce da GC in Java?

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.

Cos'è Stop-The-World in GC?

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.

Come rilevare una perdita di memoria in Android?

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.

Quando si verifica Full GC e perché è pericoloso?

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.

In che modo Kotlin aiuta a evitare le perdite di memoria?

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

  • Garbage Collection — gestione automatica della memoria rimuovendo oggetti irraggiungibili, fondamento di Android Runtime
  • Mark-and-Sweep — algoritmo di base con raccolta a due fasi, soffre di frammentazione dell'heap
  • Copying Collection — elimina la frammentazione copiando oggetti vivi in un semispazio compatto
  • Generational GC — divide l'heap in generazioni (Young/Old), accelerando la raccolta di oggetti giovani a breve durata
  • ART in Android — collettore generazionale con compattazione concorrente e pause di 2–4 ms, ha sostituito Dalvik in Android 5.0
  • Ottimizzazione GC — riduzione delle allocazioni, pool di oggetti, primitivi invece di wrapper e SparseArray invece di HashMap riducono il carico del collettore
  • Diagnostica — Android Studio Profiler, systrace e LeakCanary sono i principali strumenti per identificare problemi di memoria

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.

Discuti il progetto

Leggi anche