2 min di lettura
La memoria nella JVM· Parte 1 di 1Come funziona davvero il garbage collector
Generazioni, pause e collector moderni: cosa fa la JVM con la memoria che non usi più e come capire se il GC è il tuo problema.
Ciao a tutti. Oggi parliamo di una cosa che usiamo ogni giorno senza pensarci: il garbage collector. Quasi tutti sappiamo che “libera la memoria”, ma pochi sanno cosa succede davvero quando la nostra applicazione si blocca per mezzo secondo senza una ragione apparente.
L’idea di fondo: la maggior parte degli oggetti muore giovane
Il garbage collector della JVM si basa su un’osservazione empirica chiamata ipotesi generazionale: la gran parte degli oggetti viene usata per pochissimo tempo e poi buttata. Pensa a una stringa creata per formattare un log, o a un oggetto di appoggio dentro un ciclo.
Per questo la memoria viene divisa in due zone principali:
- Young generation: dove nascono tutti gli oggetti. Viene ripulita spesso ed è molto veloce, perché quasi tutto lì dentro è già spazzatura.
- Old generation: dove finiscono gli oggetti sopravvissuti a più pulizie. Viene ripulita raramente, ma quando succede costa di più.
Cosa significa “stop the world”
Per spostare e contare gli oggetti in sicurezza, il collector deve a volte fermare tutti i thread dell’applicazione. Questa è la famosa pausa stop-the-world. Una pausa di pochi millisecondi nessuno la nota; una da due secondi sì, soprattutto in un servizio che risponde a richieste in tempo reale.
Quale collector scegliere
Da Java 9 il collector predefinito è G1, pensato per bilanciare throughput e latenza su heap medio-grandi. Se la latenza è critica esistono collector come ZGC, che lavora quasi tutto in modo concorrente e mantiene le pause sotto il millisecondo anche con heap enormi.
Prima di cambiare collector, però, misura. Puoi attivare il log del GC con un solo parametro:
java -Xlog:gc*:file=gc.log -jar app.jar
Se nel log vedi pause lunghe e frequenti, o la old generation che cresce senza mai scendere, allora il problema è reale. Spesso la causa non è il collector ma una memory leak nel codice: oggetti che teniamo referenziati per errore, come una cache che non svuota mai nulla.
// Una cache che cresce per sempre: ogni chiave nuova resta in memoria finché l'applicazione vive
private static final Map<String, Report> CACHE = new HashMap<>();
Report load(String id) {
return CACHE.computeIfAbsent(id, this::buildReport);
}
In sintesi
Non tuning alla cieca: attiva il log, leggi i dati, e solo dopo scegli se cambiare collector o correggere il codice. Nel prossimo video vediamo come analizzare un heap dump.
