.## 1. Introduzione
Quando sviluppi in Java, ti è mai capitato che la tua applicazione si arresti improvvisamente e la console mostri:
java.lang.OutOfMemoryError: Java heap space
Questo errore significa “Java ha esaurito la memoria utilizzabile (l’heap).”
Tuttavia, dal solo messaggio di errore non è immediatamente evidente:
- Cosa ha causato l’esaurimento dell’heap
- Cosa dovresti regolare, e come
- Se il problema è nel codice o nella configurazione
Di conseguenza, le persone ricorrono spesso a “soluzioni rapide” come “basta aumentare -Xmx” o “aggiungere più memoria al server.”
Ma aumentare la dimensione dell’heap senza comprendere la causa radice non è solo una soluzione non reale—può anche innescare altri problemi.
- La GC (garbage collection) diventa più pesante e i tempi di risposta peggiorano
- La memoria complessiva del server si restringe e influisce sugli altri processi
- Una vera perdita di memoria persiste, e l’OutOfMemoryError si verifica di nuovo
Ecco perché “java heap space” non è solo “memoria insufficiente.”
Dovresti considerarlo come un segnale di un problema complesso che coinvolge il design dell’applicazione, l’implementazione e le impostazioni dell’infrastruttura.
- 1 2. Cos’è l’Heap Java?
- 1.1 2-1. Visione d’insieme delle aree di memoria Java
- 1.2 2-2. Il ruolo dell’heap
- 1.3 2-3. Cosa succede quando l’heap si esaurisce?
- 1.4 2-4. “Basta aumentare l’heap” è a metà giusto e a metà sbagliato
- 1.5 2-5. Layout dell’heap (Eden / Survivor / Old)
- 1.6 2-6. Perché la carenza di heap è comune per principianti e sviluppatori intermedi
- 2 3. Cause comuni dell’errore “java heap space”
- 2.1 3-1. Pressione di memoria dal caricamento di grandi dati
- 2.2 3-2. Accumulo Eccessivo di Dati nelle Collezioni
- 2.3 3-3. Memory Leak (Ritenzione Non Intenzionale di Oggetti)
- 2.4 3-4. Dimensione Heap JVM Troppo Piccola (I Valori Predefiniti Sono Piccoli)
- 2.5 3-5. Pattern a Lungo Termine Dove Gli Oggetti Continuano ad Accumularsi
- 2.6 3-6. Comprendere male i limiti nei container (Docker / Kubernetes)
- 3 4. Come verificare la dimensione dell’heap
- 3.1 4-1. Verifica la dimensione dell’heap dalla linea di comando
- 3.2 4-2. Verifica la dimensione dell’heap dall’interno di un programma in esecuzione
- 3.3 4-3. Verifica usando strumenti come VisualVM o Mission Control
- 3.4 4-4. Verifica negli IDE (Eclipse / IntelliJ)
- 3.5 4-5. Verifica nei server applicativi (Tomcat / Jetty)
- 3.6 4-6. Controllare l’Heap in Docker / Kubernetes (Importante)
- 3.7 4-7. Riepilogo: Controllare l’Heap è il Primo Passo Obbligatorio
- 4 5. Soluzione #1: Aumentare la Dimensione dell’Heap
- 4.1 5-1. Aumentare la Dimensione dell’Heap dalla Linea di Comando
- 4.2 5-2. Configurazione per Applicazioni Server Residenti (Tomcat / Jetty, ecc.)
- 4.3 5-3. Impostazioni dell’Heap per le App Spring Boot
- 4.4 5-4. Impostazioni dell’Heap in Docker / Kubernetes (Importante)
- 4.5 5-5. Esempi di Impostazioni dell’Heap per Ambienti CI/CD e Cloud
- 4.6 5-6. L’aumento dell’Heap risolve sempre? → Ci sono dei limiti
- 5 6. Soluzione #2: Ottimizza il tuo Codice
- 5.1 6-1. Ripensa a Come Usi le Collezioni
- 5.2 6-2. Evita l’Elaborazione Bulk di Dati di Grandi Dimensioni (Elabora a Blocchi)
- 5.3 6-3. Evita la Creazione Inutile di Oggetti
- 5.4 6-4. Fai Attenzione alla Concatenazione di Stringhe
- 5.5 6-5. Non sovraccaricare le cache
- 5.6 6-6. Non ricreare oggetti all’interno di grandi cicli
- 5.7 6-7. Dividere il lavoro ad alta intensità di memoria in processi separati
- 5.8 6-8. L’ottimizzazione del codice è un passo chiave per prevenire ricorrenze
- 6 7. Soluzione n. 3: Ottimizzare GC (Garbage Collection)
- 7 7-1. Che cos’è GC (Garbage Collection)?
- 8 7-2. Tipi di GC e caratteristiche (come scegliere)
- 9 7-3. Come cambiare esplicitamente GC
- 10 7-4. Generare Log GC e Ispezionare Visivamente i Problemi
- 11 7-5. Casi in cui la Latenza del GC Innesca “java heap space”
- 12 7-6. Punti Chiave nella Messa a Punto di G1GC
- 13 7-7. Riepilogo della Messa a Punto del GC
- 14 8. Soluzione #4: Rilevare le Perdite di Memoria
- 15 8-1. Che Cos’è una Perdita di Memoria? (Sì, Succede in Java)
- 16 8-2. Modelli Tipici di Perdite di Memoria
- 17 8-3. Punti di Controllo per Rilevare i “Segni” di una Perdita di Memoria
- 18 8-4. Tool #1: Controllo Visivo delle Perdite con VisualVM
- 19 8-5. Tool #2: Analisi Approfondita con Eclipse MAT (Memory Analyzer Tool)
- 20 8-6. Se Capisci l’Albero del Dominatore, l’Analisi Accelera Drammaticamente
- 21 8-7. Come Catturare un Heap Dump (da Riga di Comando)
- 22 8-8. La Correzione Vera di una Perdita Richiede Modifiche al Codice
- 23 8-9. Come Distinguere “Carenza di Heap” vs “Perdita di Memoria”
- 24 8-10. Riassunto: Se l’Ottimizzazione dell’Heap Non Risolve OOM, Sospetta una Perdita
- 25 9. Problemi di “java heap space” in Docker / Kubernetes e Come Risolverli
- 26 9-1. Perché gli Errori di Heap Space Sono Così Comuni nei Container
- 27 9-2. Miglioramenti in Java 8u191+ e Java 11+
- 28 9-3. Impostazione Esplicita della Dimensione dell’Heap nei Container (Richiesta)
- 29 9-4. Insidie nelle Impostazioni di Memoria in Kubernetes (K8s)
- 30 9-5. Percentuale Automatica dell’Heap in Java 11+ (MaxRAMPercentage)
- 31 9-6. Perché OOMKilled Si Verifica Così Spesso nei Container (Pattern del Mondo Reale)
- 32 9-7. Checkpoints Specifici per i Container Utilizzando Log GC e Metriche
- 33 9-8. Riepilogo: le “Impostazioni Esplicite” sono le predefinite nei container
- 34 10. Anti‑Pattern da Evitare (Codice Errato / Impostazioni Errate)
- 35 10-1. Lasciare Collezioni Illimitate Crescere All’infinito
- 36 10-2. Caricare File o Dati Enormi Tutti in Una Volta
- 37 10-3. Continuare a Tenere Dati in Variabili statiche
- 38 10-4. Uso Eccessivo di Stream / Lambda e Generazione di Enormi Liste Intermedie
- 39 10-5. Eseguire Massicce Concatenazioni di Stringhe con l’Operatore +
- 40 10-6. Creare Troppe Cache e Non Gestirle
- 41 10-7. Mantenere Log o Statistiche in Memoria Continuamente
- 42 10-8. Non Specificare -Xmx in Container Docker
- 43 10-9. Sovra-Tuning delle Impostazioni GC
- 44 10-10. Riassunto: La Maggior Parte degli Anti-Patterns Deriva da “Memorizzare Troppo”
- 45 11. Esempi Reali: Questo Codice È Pericoloso (Pattern Tipici di Problemi di Memoria)
- 46 11-1. Caricamento in Blocco di Dati Enormi
- 47 11-2. Pattern di Gonfiamento della Collezione
- 48 11-3. Generare Troppi Oggetti Intermedi via Stream API
- 49 11-4. Parsing di JSON o XML Tutto in Una Volta
- 50 11-5. Caricamento di Tutte le Immagini / Dati Binari in Memoria
- 51 11-6. Ritenzione Infinita tramite Cache Statico
- 52 11-7. Uso Sbagliato di ThreadLocal
- 53 11-8. Creazione di Troppi Exception
- 54 11-9. Riassunto: Codice Pericoloso “Silenziosamente” Mangia il Tuo Heap
- 55 12. Best Practices per la Gestione della Memoria in Java (Essenziali per Prevenire Ricorrenze)
- 56 12-1. Imposta la Dimensione dell’Heap Esplicitamente (Specialmente in Produzione)
- 57 12-2. Monitora Appropriatamente (GC, Utilizzo Memoria, OOM)
- 58 12-3. Usa “Cache controllate”
- 59 12-4. Fai attenzione all’uso eccessivo di Stream API e Lambda
- 60 12-5. Converti file / dati enormi in streaming
- 61 12-6. Gestisci ThreadLocal con attenzione
- 62 12-7. Cattura periodicamente i dump di heap per rilevare le perdite in anticipo
- 63 12-8. Mantieni la sintonizzazione del GC al minimo
- 64 12-9. Considera di suddividere l’architettura
- 65 12-10. Riepilogo: La gestione della memoria Java riguarda l’ottimizzazione a più livelli
- 66 13. Riepilogo: Punti chiave per prevenire gli errori “java heap space”
- 67 13-1. Il vero problema non è “Heap troppo piccolo” ma “Perché si esaurisce?”
- 68 13-2. Primi Passi per Indagare
- 69 13-3. Modelli ad Alto Rischio Comuni in Produzione
- 70 13-4. Le Correzioni Fondamentali Riguardano il Design del Sistema e l’Elaborazione dei Dati
- 71 13-5. I Tre Messaggi Più Importanti
- 72 13-6. La Gestione della Memoria Java è una Competenza che Crea un Vero Vantaggio
- 73 14. FAQ
- 74 Q1. Qual è la differenza tra java.lang.OutOfMemoryError: Java heap space e GC overhead limit exceeded?
- 75 Q2. Se semplicemente aumento l’heap, sarà risolto?
- 76 Q3. Quanto posso aumentare l’heap Java?
- 77 Q4. Perché Java riceve OOMKilled nei container (Docker/K8s)?
- 78 Q5. C’è un modo semplice per capire se si tratta di una perdita di memoria?
- 79 Q6. Le impostazioni dell’heap in Eclipse / IntelliJ non vengono applicate
- 80 Q7. È vero che Spring Boot utilizza molta memoria?
- 81 Q8. Quale GC dovrei usare?
- 82 Q9. Come dovrei gestire l’heap negli ambienti serverless (Cloud Run / Lambda)?
- 83 Q10. Come posso prevenire il ripetersi dei problemi di heap Java?
- 84 Riepilogo: Usa le FAQ per Eliminare i Dubbi e Applicare Contromisure Pratiche sulla Memoria
1-1. Pubblico di Destinazione
Questo articolo è destinato ai lettori che:
- Conoscono le basi di Java (classi, metodi, collezioni, ecc.)
- Ma non comprendono appieno come la memoria è gestita all’interno della JVM
- Hanno incontrato “java heap space” o
OutOfMemoryErrorin sviluppo/test/produzione—oppure vogliono essere preparati - Eseguono Java su Docker/container/cloud e si sentono leggermente incerti sulle impostazioni di memoria
I tuoi anni di esperienza con Java non contano.
Se vuoi “comprendere correttamente l’errore e imparare a isolare la causa da solo,” questa guida mira a essere direttamente utile nel lavoro reale.
1-2. Cosa Imparerai in Questo Articolo
In questo articolo, spieghiamo l’errore “java heap space” dal meccanismo verso l’alto—non solo una lista di correzioni.
Gli argomenti chiave includono:
Cos’è l’heap Java wp:list /wp:list
- Come differisce dallo stack
- Dove vengono allocate le istanze
Modelli comuni che causano “java heap space” wp:list /wp:list
Caricamento massivo di grandi quantità di dati
- Collezioni e cache che crescono eccessivamente
- Perdite di memoria (codice che mantiene vive le referenze)
Come verificare e aumentare la dimensione dell’heap wp:list /wp:list
Opzioni da riga di comando (
-Xms,-Xmx)- Impostazioni IDE (Eclipse / IntelliJ, ecc.)
- Punti di configurazione del server applicativo (Tomcat, ecc.)
Tecniche per risparmiare memoria nel codice wp:list /wp:list
Riesaminare come usi le collezioni
- Insidie nell’uso di stream e lambda
- Strategie di chunking per grandi dati
La relazione tra GC e l’heap wp:list /wp:list
Come funziona fondamentalmente la GC
- Come leggere i log della GC a livello base
Rilevare perdite di memoria e usare gli strumenti wp:list /wp:list
Ottenere un dump dell’heap
- Iniziare l’analisi usando VisualVM o Eclipse MAT
Cose da tenere d’occhio negli ambienti container (Docker / Kubernetes) wp:list /wp:list
La relazione tra container e
-Xmx- Limiti di memoria tramite cgroups e l’OOM Killer
Nella seconda metà dell’articolo, rispondiamo anche a domande comuni in formato FAQ, come:
- “Devo semplicemente aumentare l’heap per ora?”
- “Quanto posso aumentare in sicurezza l’heap?”
- “Come posso approssimativamente capire se è una perdita di memoria?”
1-3. Come Leggere Questo Articolo
L’errore “java heap space” è importante sia per le persone che:
- Hanno bisogno di risolvere subito un incidente in produzione
- Vogliono prevenire i problemi prima che si verifichino
Se hai bisogno di una correzione immediata, puoi saltare alle sezioni pratiche come:
- Come modificare la dimensione dell’heap
- Come verificare le perdite di memoria
D’altra parte, se vuoi una comprensione approfondita, leggi in quest’ordine:
- Le basi: “Cos’è l’heap Java”
- Cause tipiche
- Poi soluzioni e passaggi di ottimizzazione
Questo flusso ti aiuterà a comprendere chiaramente il meccanismo dietro l’errore.
2. Cos’è l’Heap Java?
final answer.Per comprendere correttamente l’errore “java heap space”, è necessario prima sapere come Java gestisce la memoria.
In Java, la memoria è suddivisa in più aree in base allo scopo, e tra queste l’heap svolge un ruolo critico come spazio di memoria per gli oggetti.
2-1. Visione d’insieme delle aree di memoria Java
Le applicazioni Java vengono eseguite sulla JVM (Java Virtual Machine).
La JVM dispone di più aree di memoria per gestire diversi tipi di dati. Le tre più comuni sono:
■ Tipi di aree di memoria
- Heap L’area in cui vengono memorizzati gli oggetti creati dall’applicazione. Se si esaurisce, si ottiene l’errore “java heap space”.
- Stack L’area per le chiamate di metodo, le variabili locali, i riferimenti e altro. Se si sfora, si ottiene “StackOverflowError.”
- Method Area / Metaspace Memorizza le informazioni delle classi, le costanti, i metadati e i risultati della compilazione JIT.
In Java, tutti gli oggetti creati con new vengono collocati sull’heap.
2-2. Il ruolo dell’heap
L’heap Java è dove vengono memorizzate cose come le seguenti:
- Oggetti creati con
new - Array (inclusi i contenuti di List/Map, ecc.)
- Oggetti generati internamente dalle lambda
- Stringhe e buffer usati da StringBuilder
- Strutture dati utilizzate all’interno del framework delle collezioni
In altre parole, quando Java ha bisogno di “tenere qualcosa in memoria”, quasi sempre viene memorizzato sull’heap.
2-3. Cosa succede quando l’heap si esaurisce?
Se l’heap è troppo piccolo—o l’applicazione crea troppi oggetti—Java esegue GC (garbage collection) per recuperare memoria rimuovendo gli oggetti non più utilizzati.
Ma se una GC ripetuta non riesce ancora a liberare abbastanza memoria, e la JVM non può più allocare memoria, otterrai:
java.lang.OutOfMemoryError: Java heap space
e l’applicazione sarà costretta a fermarsi.
2-4. “Basta aumentare l’heap” è a metà giusto e a metà sbagliato
Se l’heap è semplicemente troppo piccolo, aumentarlo può risolvere il problema—ad esempio:
-Xms1024m -Xmx2048m
Tuttavia, se la causa principale è una memory leak o un elaborazione inefficiente di grandi quantità di dati nel codice, aumentare l’heap compra solo tempo e non risolve il problema di fondo.
In breve, capire “perché l’heap si sta esaurendo” è la cosa più importante.
2-5. Layout dell’heap (Eden / Survivor / Old)
L’heap Java è ampiamente diviso in due parti:
Young generation (oggetti appena creati) wp:list /wp:list
- Eden
- Survivor (S0, S1)
- Old generation (oggetti a lunga vita)
Il GC funziona in modo diverso a seconda dell’area.
Generazione giovane
Gli oggetti vengono prima collocati in Eden, e gli oggetti a breve vita vengono rapidamente rimossi.
Il GC viene eseguito frequentemente qui, ma è relativamente leggero.
Generazione vecchia
Gli oggetti che sopravvivono a sufficienza vengono promossi da Young a Old.
Il GC nella generazione vecchia è più costoso, quindi se quest’area continua a crescere, può causare latenza o pause.
In molti casi, un errore “heap space” si verifica alla fine perché la generazione Old si riempie.
2-6. Perché la carenza di heap è comune per principianti e sviluppatori intermedi
Poiché Java esegue la garbage collection automaticamente, le persone spesso presumono che “la JVM gestisca tutta la gestione della memoria.”
In realtà, ci sono molti modi per esaurire l’heap, come ad esempio:
- Codice che continua a creare un gran numero di oggetti
- Riferimenti mantenuti vivi all’interno delle collezioni
- Stream/lambda che generano involontariamente enormi quantità di dati
- Cache sovradimensionate
- Comprensione errata dei limiti di heap nei container Docker
- Configurazione errata dell’heap in un IDE
Ecco perché imparare come funziona l’heap stesso è il percorso più breve verso una correzione affidabile.
3. Cause comuni dell’errore “java heap space”
La carenza di heap è un problema frequente in molti ambienti reali, ma le sue cause possono essere in gran parte raggruppate in tre categorie: volume di dati, codice/design e configurazione errata.
In questa sezione, organizziamo i pattern tipici e spieghiamo perché portano all’errore.
3-1. Pressione di memoria dal caricamento di grandi dati
Il pattern più comune è quando i dati stessi sono così grandi che l’heap si esaurisce.
■ Esempi Comuni
- Caricamento di un enorme CSV/JSON/XML tutto in una volta in memoria
- Recupero di un numero massiccio di record dal database in un’unica operazione
- Un’API Web restituisce una risposta molto grande (immagini, log, ecc.)
Uno scenario particolarmente pericoloso è:
Quando la “stringa grezza prima del parsing” e gli “oggetti dopo il parsing” esistono in memoria contemporaneamente.
Ad esempio, se carichi un JSON da 500MB come singola stringa e poi lo deserializzi con Jackson, l’uso totale della memoria può facilmente superare 1GB.
■ Direzione per la Mitigazione
- Introduci lettura chunked (elaborazione streaming)
- Usa paging per l’accesso al database
- Evita di mantenere i dati intermedi più a lungo del necessario
Seguire la regola “gestisci i dati grandi in chunk” è un ottimo modo per prevenire l’esaurimento dell’heap.
3-2. Accumulo Eccessivo di Dati nelle Collezioni
Questo è estremamente comune per sviluppatori principianti o intermedi.
■ Errori Tipici
- Aggiunta continua di log o dati temporanei a una
List→ cresce senza essere pulita - Uso di una
Mapcome cache (ma senza mai rimuovere le entry) - Creazione continua di nuovi oggetti all’interno di loop
- Generazione di un numero enorme di oggetti temporanei tramite Stream o lambda
In Java, finché rimane un riferimento, il GC non può rimuovere l’oggetto.
In molti casi, gli sviluppatori mantengono involontariamente i riferimenti attivi.
■ Direzione per la Mitigazione
- Definisci un ciclo di vita per le cache
- Imposta limiti di capacità per le collezioni
- Per meccanismi di grandi dati, pulisci periodicamente
Per riferimento, anche se non “sembra” una memory leak:
List<String> list = new ArrayList<>();
for (...) {
list.add(heavyData); // ← grows forever
}
Questo tipo di codice è molto pericoloso.
3-3. Memory Leak (Ritenzione Non Intenzionale di Oggetti)
Poiché Java ha il GC, le persone spesso pensano che “le memory leak non accadano in Java.”
In pratica, le memory leak accadono assolutamente in Java.
■ Hotspot Comuni per le Leak
- Mantenere oggetti in variabili statiche
- Dimenticare di unregisterare listener o callback
- Lasciare riferimenti attivi all’interno di Stream/Lambda
- Accumulo di oggetti in job batch a lungo termine
- Memorizzazione di grandi dati in ThreadLocal e il thread viene riutilizzato
Le memory leak non sono qualcosa che puoi evitare completamente in Java.
■ Direzione per la Mitigazione
- Rivaluta come usi le variabili statiche
- Assicurati che
removeListener()eclose()siano sempre chiamati - Per processi a lungo termine, prendi un heap dump e investiga
- Evita ThreadLocal a meno che non sia veramente necessario
Poiché le memory leak ricorreranno anche se aumenti l’heap,
l’indagine sulla causa radice è essenziale.
3-4. Dimensione Heap JVM Troppo Piccola (I Valori Predefiniti Sono Piccoli)
A volte l’applicazione è a posto, ma l’heap stesso è semplicemente troppo piccolo.
La dimensione heap predefinita varia in base a OS e versione di Java.
In Java 8, è comunemente impostata a circa 1/64 a 1/4 della memoria fisica.
Una configurazione pericolosa spesso vista in produzione è:
No -Xmx specified, while the app processes large data
■ Scenari Comuni
- Solo in produzione c’è un volume di dati maggiore, e l’heap predefinito non basta
- Esecuzione su Docker senza impostare
-Xmx - Spring Boot avviato come fat JAR con valori predefiniti
■ Direzione per la Mitigazione
- Imposta
-Xmse-Xmxa valori appropriati - Nei container, comprendi memoria fisica vs limiti cgroup e configura di conseguenza
3-5. Pattern a Lungo Termine Dove Gli Oggetti Continuano ad Accumularsi
Applicazioni come le seguenti tendono ad accumulare pressione sulla memoria nel tempo:
- Applicazioni Spring Boot a lungo termine
- Job batch intensivi sulla memoria
- Applicazioni Web con grande traffico utente
I job batch in particolare mostrano spesso questo pattern:
- La memoria viene consumata
- Il GC recupera a malapena abbastanza
- Rimane un po’ di accumulo, e l’esecuzione successiva causa OOM
.This leads to many delayed-onset heap space errors.
3-6. Comprendere male i limiti nei container (Docker / Kubernetes)
C’è una trappola comune in Docker/Kubernetes:
■ Pitfall
- Non impostare
-Xmx→ Java fa riferimento alla memoria fisica dell’host anziché al limite del container → ne usa troppa → il processo viene terminato dall’OOM Killer
Questo è uno degli incidenti di produzione più comuni.
■ Mitigation
- Impostare
-XX:MaxRAMPercentagein modo appropriato - Allineare
-Xmxal limite di memoria del container - Comprendere “UseContainerSupport” in Java 11+
4. Come verificare la dimensione dell’heap
Quando vedi un errore “java heap space”, la prima cosa da fare è confermare quanta heap è attualmente allocata.
In molti casi, l’heap è semplicemente più piccola di quanto ci si aspetti — quindi la verifica è un passo critico iniziale.
In questa sezione, copriamo i modi per verificare la dimensione dell’heap da linea di comando, all’interno del programma, IDE e server applicativi.
4-1. Verifica la dimensione dell’heap dalla linea di comando
Java fornisce diverse opzioni per controllare i valori di configurazione della JVM all’avvio.
■ Uso di -XX:+PrintFlagsFinal
Questo è il modo più affidabile per confermare la dimensione dell’heap:
java -XX:+PrintFlagsFinal -version | grep HeapSize
Vedrai un output simile a:
- InitialHeapSize … la dimensione iniziale dell’heap specificata con
-Xms - MaxHeapSize … la dimensione massima dell’heap specificata con
-Xmx
Esempio:
uintx InitialHeapSize = 268435456
uintx MaxHeapSize = 4294967296
Ciò significa:
- Heap iniziale: 256 MB
- Heap massima: 4 GB
■ Esempio concreto
java -Xms512m -Xmx2g -XX:+PrintFlagsFinal -version | grep HeapSize
Questo è utile anche dopo aver modificato le impostazioni, rendendolo un metodo di conferma affidabile.
4-2. Verifica la dimensione dell’heap dall’interno di un programma in esecuzione
A volte vuoi verificare la quantità di heap dall’interno dell’applicazione in esecuzione.
Java lo rende semplice usando la classe Runtime:
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
long free = Runtime.getRuntime().freeMemory();
System.out.println("Max Heap: " + (max / 1024 / 1024) + " MB");
System.out.println("Total Heap: " + (total / 1024 / 1024) + " MB");
System.out.println("Free Heap: " + (free / 1024 / 1024) + " MB");
- maxMemory() … la dimensione massima dell’heap (
-Xmx) - totalMemory() … l’heap attualmente allocata dalla JVM
- freeMemory() … lo spazio attualmente disponibile all’interno di quell’heap
Per le app web o processi a lunga durata, registrare questi valori può aiutare durante l’indagine di un incidente.
4-3. Verifica usando strumenti come VisualVM o Mission Control
Puoi anche ispezionare visivamente l’uso dell’heap con strumenti GUI.
■ VisualVM
- Visualizzazione in tempo reale dell’uso dell’heap
- Tempistiche del GC
- Cattura del dump dell’heap
È uno strumento classico, comunemente usato nello sviluppo Java.
■ Java Mission Control (JMC)
- Consente un profiling più dettagliato
- Particolarmente utile per operazioni su Java 11+
Questi strumenti ti aiutano a visualizzare problemi come solo la generazione Old che cresce.
4-4. Verifica negli IDE (Eclipse / IntelliJ)
Se esegui la tua app da un IDE, le impostazioni dell’IDE possono influenzare la dimensione dell’heap.
■ Eclipse
Window → Preferences → Java → Installed JREs
Oppure imposta -Xms / -Xmx sotto:
Run Configuration → VM arguments
■ IntelliJ IDEA
Help → Change Memory Settings
Oppure aggiungi -Xmx sotto le opzioni VM in Run/Debug Configuration.
Fai attenzione — a volte l’IDE stesso impone un limite di heap.
4-5. Verifica nei server applicativi (Tomcat / Jetty)
Per le applicazioni web, la dimensione dell’heap è spesso specificata negli script di avvio del server.
■ Tomcat Example (Linux)
CATALINA_OPTS="-Xms512m -Xmx2g"
■ Tomcat Example (Windows)
set JAVA_OPTS=-Xms512m -Xmx2g
In produzione, lasciare questo ai valori predefiniti è comune—e spesso porta a errori di spazio heap dopo che il servizio è stato in esecuzione per un po’.
4-6. Controllare l’Heap in Docker / Kubernetes (Importante)
Nei container, memoria fisica, cgroup e impostazioni Java interagiscono in modi complessi.
In Java 11+, “UseContainerSupport” può regolare automaticamente l’heap, ma il comportamento può comunque essere inaspettato a seconda di:
- Il limite di memoria del container (ad es.,
--memory=512m) - Se
-Xmxè impostato esplicitamente
Ad esempio, se imposti solo un limite di memoria per il container:
docker run --memory=512m ...
e non imposti -Xmx, potresti incorrere in:
- Java fa riferimento alla memoria dell’host e tenta di allocare troppo
- i cgroup applicano il limite
- Il processo viene terminato dall’OOM Killer
Questo è un problema di produzione molto comune.
4-7. Riepilogo: Controllare l’Heap è il Primo Passo Obbligatorio
Le carenze di heap richiedono soluzioni molto diverse a seconda della causa. Inizia comprendendo, come insieme:
- La dimensione attuale dell’heap
- L’utilizzo reale
- Visualizzazione tramite strumenti
5. Soluzione #1: Aumentare la Dimensione dell’Heap
La risposta più diretta a un errore “java heap space” è aumentare la dimensione dell’heap. Se la causa è una semplice carenza di memoria, aumentare l’heap in modo appropriato può ripristinare il comportamento normale.
Tuttavia, quando aumenti l’heap, è importante comprendere sia i metodi di configurazione corretti sia le precauzioni chiave. Impostazioni errate possono portare a degrado delle prestazioni o ad altri problemi OOM (Out Of Memory).
5-1. Aumentare la Dimensione dell’Heap dalla Linea di Comando
Se avvii un’applicazione Java come JAR, il metodo più semplice è specificare -Xms e -Xmx:
■ Esempio: Iniziale 512MB, Max 2GB
java -Xms512m -Xmx2g -jar app.jar
-Xms… la dimensione iniziale dell’heap riservata all’avvio della JVM-Xmx… la dimensione massima dell’heap che la JVM può utilizzare
In molti casi, impostare -Xms e -Xmx allo stesso valore aiuta a ridurre l’overhead dovuto al ridimensionamento dell’heap.
Esempio:
java -Xms2g -Xmx2g -jar app.jar
5-2. Configurazione per Applicazioni Server Residenti (Tomcat / Jetty, ecc.)
Per le applicazioni web, imposta queste opzioni negli script di avvio del server applicativo.
■ Tomcat (Linux)
Imposta in setenv.sh:
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx2048m"
■ Tomcat (Windows)
Imposta in setenv.bat:
set CATALINA_OPTS=-Xms512m -Xmx2048m
■ Jetty
Aggiungi quanto segue a start.ini o jetty.conf:
--exec
-Xms512m
-Xmx2048m
Poiché le app web possono avere picchi di utilizzo della memoria a seconda del traffico, la produzione dovrebbe generalmente avere più margine rispetto agli ambienti di test.
5-3. Impostazioni dell’Heap per le App Spring Boot
Se esegui Spring Boot come un fat JAR, le basi sono le stesse:
java -Xms1g -Xmx2g -jar spring-app.jar
Spring Boot tende a utilizzare più memoria rispetto a un semplice programma Java perché carica molte classi e configurazioni all’avvio.
Spesso consuma più memoria di una tipica applicazione Java.
5-4. Impostazioni dell’Heap in Docker / Kubernetes (Importante)
Per Java nei container, è necessario fare attenzione perché i limiti del container e il calcolo dell’heap della JVM interagiscono.
■ Recommended Example (Docker)
docker run --memory=1g \
-e JAVA_OPTS="-Xms512m -Xmx800m" \
my-java-app
■ Perché Devi Impostare Esplicitamente -Xmx
Se non specifichi -Xmx in Docker:
- La JVM decide la dimensione dell’heap basandosi sulla memoria fisica della macchina host, non sul container
- Potrebbe tentare di allocare più memoria di quella consentita dal container
- Raggiunge il limite di memoria del cgroup e il processo viene terminato dall’OOM Killer
Poiché questo è un problema di produzione molto comune, dovresti sempre impostare -Xmx negli ambienti containerizzati.
5-5. Esempi di Impostazioni dell’Heap per Ambienti CI/CD e Cloud
In ambienti Java basati su cloud, una regola pratica comune è impostare l’heap in base alla memoria disponibile:
| Total Memory | Recommended Heap (Approx.) |
|---|---|
| 1GB | 512–800MB |
| 2GB | 1.2–1.6GB |
| 4GB | 2–3GB |
| 8GB | 4–6GB |
※ Lascia la memoria rimanente per il sistema operativo, l’overhead del GC e gli stack dei thread.
Negli ambienti cloud, la memoria totale può essere limitata. Se aumenti l’heap senza pianificazione, l’intera applicazione può diventare instabile.
5-6. L’aumento dell’Heap risolve sempre? → Ci sono dei limiti
Aumentare la dimensione dell’heap può eliminare temporaneamente l’errore, ma non risolve casi come:
- Esiste una perdita di memoria
- Una collezione continua a crescere all’infinito
- Vengono elaborati dati enormi in blocco
- L’app ha un design errato
Quindi considera l’aumento dell’heap come una misura d’emergenza, e assicurati di seguirlo con ottimizzazione del codice e rivedere il design di elaborazione dei dati, che tratteremo nel prossimo capitolo.
6. Soluzione #2: Ottimizza il tuo Codice
Aumentare la dimensione dell’heap può essere una mitigazione efficace, ma se la causa principale risiede nella struttura del tuo codice o nel modo in cui elabori i dati, l’errore “java heap space” ritornerà prima o dopo.
In questa sezione, copriremo i pattern di codifica comuni nel mondo reale che sfruttano inutilmente la memoria, e approcci concreti per migliorarli.
6-1. Ripensa a Come Usi le Collezioni
Le collezioni Java (List, Map, Set, ecc.) sono comode, ma un uso sconsiderato può facilmente diventare la causa principale della crescita della memoria.
■ Pattern ①: List / Map Crescono Senza Limiti
Un esempio comune:
List<String> logs = new ArrayList<>();
while (true) {
logs.add(fetchLog()); // ← grows forever
}
Le collezioni con nessuna condizione di terminazione chiara o limite superiore comprimeranno costantemente l’heap in ambienti a lunga esecuzione.
● Miglioramenti
- Usa una collezione limitata (ad esempio, imposta un limite di dimensione e scarta le voci più vecchie)
- Pulisci periodicamente i valori di cui non hai più bisogno
- Se usi una Map come cache, adotta una cache con eviction → Guava Cache o Caffeine sono buone opzioni
■ Pattern ②: Non Impostare la Capacità Iniziale
ArrayList e HashMap crescono automaticamente quando superano la capacità, ma tale crescita comporta: allocare un nuovo array → copiare → scartare il vecchio array.
Quando si gestiscono grandi set di dati, omettere la capacità iniziale è inefficiente e può sprecare memoria.
● Esempio di Miglioramento
List<String> items = new ArrayList<>(10000);
Se puoi stimare la dimensione, è meglio impostarla in anticipo.
6-2. Evita l’Elaborazione Bulk di Dati di Grandi Dimensioni (Elabora a Blocchi)
Se elabori dati massivi tutti in una volta, è facile incorrere nello scenario peggiore: tutto finisce sull’heap → OOM.
■ Esempio Negativo (Leggere un Enorme File Tutto in Una Volta)
String json = Files.readString(Paths.get("large.json"));
Object data = new ObjectMapper().readValue(json, Data.class);
■ Miglioramenti
- Usa elaborazione in streaming (ad esempio, Jackson Streaming API)
- Leggi in porzioni più piccole (paginazione a batch)
- Elabora i flussi in modo sequenziale e non conservare l’intero set di dati
● Esempio: Elaborare un Enorme JSON con Jackson Streaming
JsonFactory factory = new JsonFactory();
try (JsonParser parser = factory.createParser(new File("large.json"))) {
while (!parser.isClosed()) {
JsonToken token = parser.nextToken();
// Perform only what you need, and do not retain it in memory
}
}
6-3. Evita la Creazione Inutile di Oggetti
Stream e lambda sono comodi, ma possono generare internamente un gran numero di oggetti temporanei.
■ Esempio Negativo (Creare una Enorme Lista Intermedia con Streams)
List<Result> results = items.stream()
.map(this::toResult)
.collect(Collectors.toList());
Se items è enorme, viene creato un gran numero di oggetti temporanei, e l’heap si gonfia.
● Miglioramenti
- Elabora sequenzialmente con un ciclo for
- Scrivi solo ciò di cui hai bisogno immediatamente (non conservare tutto)
- Evita
collect(), o controllalo manualmente
6-4. Fai Attenzione alla Concatenazione di Stringhe
■ Miglioramenti
.
- Usa
StringBuilderper concatenazioni pesanti - Evita concatenazioni non necessarie quando generi i log
StringBuilder sb = new StringBuilder(); for (String s : items) { sb.append(s); }
6-5. Non sovraccaricare le cache
Questa è una situazione comune nelle app web e nell’elaborazione batch:
- “Abbiamo aggiunto una cache per velocizzare.”
- → ma abbiamo dimenticato di svuotarla
- → la cache continua a crescere
- → carenza di heap → OOM
■ Miglioramenti
- Imposta TTL (scadenza basata sul tempo) e una dimensione massima
- Usare
ConcurrentHashMapcome sostituto della cache è rischioso - Utilizza una cache ben gestita come Caffeine che controlla correttamente la memoria
6-6. Non ricreare oggetti all’interno di grandi cicli
■ Esempio negativo
for (...) {
StringBuilder sb = new StringBuilder(); // created every iteration
...
}
Questo crea più oggetti temporanei del necessario.
● Miglioramento
StringBuilder sb = new StringBuilder();
for (...) {
sb.setLength(0); // reuse
}
6-7. Dividere il lavoro ad alta intensità di memoria in processi separati
Quando gestisci dati davvero massivi in Java, potresti dover rivedere l’architettura stessa.
- Separare ETL in un job batch dedicato
- Delegare a elaborazione distribuita (Spark o Hadoop)
- Suddividere i servizi per evitare contese di heap
6-8. L’ottimizzazione del codice è un passo chiave per prevenire ricorrenze
Se aumenti solo il heap, alla fine raggiungerai il prossimo “limite” e lo stesso errore si ripresenterà.
Per prevenire fondamentalmente gli errori “java heap space”, devi:
- Comprendere il volume dei tuoi dati
- Rivedere i pattern di creazione degli oggetti
- Migliorare il design delle collezioni
7. Soluzione n. 3: Ottimizzare GC (Garbage Collection)
L’errore “java heap space” può verificarsi non solo quando l’heap è troppo piccolo, ma anche quando GC non riesce a recuperare la memoria in modo efficace e l’heap gradualmente si satura.
Senza comprendere GC, è facile diagnosticare erroneamente sintomi come:
“La memoria dovrebbe essere disponibile, ma continuiamo a ricevere errori”, o “Il sistema diventa estremamente lento”.
Questa sezione spiega il meccanismo di base di GC in Java e i punti di ottimizzazione pratici che aiutano nelle operazioni reali.
7-1. Che cos’è GC (Garbage Collection)?
GC è il meccanismo di Java per scartare automaticamente gli oggetti che non sono più necessari.
L’heap di Java è ampiamente suddiviso in due generazioni, e GC si comporta diversamente in ciascuna.
● Generazione giovane (oggetti a breve vita)
- Eden / Survivor (S0, S1)
- Dati temporanei creati localmente, ecc.
- GC avviene frequentemente, ma è leggero
● Generazione vecchia (oggetti a lunga vita)
- Oggetti promossi dalla giovane
- GC è più pesante; se avviene spesso, l’app può “congelarsi”
In molti casi, “java heap space” si verifica alla fine quando la generazione vecchia si riempie.
7-2. Tipi di GC e caratteristiche (come scegliere)
Java fornisce molteplici algoritmi di GC.
Scegliere quello giusto per il tuo carico di lavoro può migliorare notevolmente le prestazioni.
● ① G1GC (Predefinito da Java 9)
- Divide l’heap in piccole regioni e le recupera in modo incrementale
- Può mantenere le pause “stop‑the‑world” più brevi
- Ottimo per app web e sistemi aziendali
→ In generale, G1GC è una scelta predefinita sicura
● ② Parallel GC (Buono per job batch ad alta produttività)
- Parallelizzato e veloce
- Ma le pause possono diventare più lunghe
- Spesso vantaggioso per l’elaborazione batch intensiva di CPU
● ③ ZGC (GC a bassa latenza con pause a livello di millisecondi)
- Disponibile da Java 11+
- Per app sensibili alla latenza (server di gioco, HFT)
- Efficace anche con heap molto grandi (decine di GB)
● ④ Shenandoah (GC a bassa latenza)
- Spesso associato alle distribuzioni Red Hat
- Può ridurre aggressivamente i tempi di pausa
- Disponibile anche in alcune build come AWS Corretto
7-3. Come cambiare esplicitamente GC
G1GC è il predefinito in molte configurazioni, ma puoi specificare un algoritmo di GC a seconda del tuo obiettivo:
# G1GC
java -XX:+UseG1GC -jar app.jar
# Parallel GC
java -XX:+UseParallelGC -jar app.jar
# ZGC
java -XX:+UseZGC -jar app.jar
Poiché l’algoritmo GC può cambiare drasticamente il comportamento dell’heap e il tempo di pausa, i sistemi di produzione spesso lo impostano esplicitamente.
7-4. Generare Log GC e Ispezionare Visivamente i Problemi
È fondamentale capire quanta memoria il GC sta recuperando e con quale frequenza si verificano le pause stop-the-world.
● Configurazione Base del Logging GC
java \
-Xms1g -Xmx1g \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-Xloggc:gc.log \
-jar app.jar
Esaminando gc.log, è possibile identificare chiari segni di pressione sull’heap, come:
- Troppi Young GC
- La generazione Old non diminuisce mai
- Il Full GC si verifica frequentemente
- Ogni GC recupera una quantità insolitamente piccola
7-5. Casi in cui la Latenza del GC Innesca “java heap space”
Se la pressione sull’heap è causata da schemi come i seguenti, il comportamento del GC diventa un indizio decisivo.
● Sintomi
- L’applicazione si blocca improvvisamente
- Il GC gira per secondi fino a decine di secondi
- La generazione Old continua a crescere
- Il Full GC aumenta, e infine si verifica OOM
Ciò indica uno stato in cui il GC sta facendo del suo meglio, ma non riesce a recuperare abbastanza memoria prima di raggiungere il limite.
■ Cause Principali Comuni
- Perdite di memoria
- Collezioni mantenute permanentemente
- Oggetti che vivono troppo a lungo
- Inflazione della generazione Old
In questi casi, l’analisi dei log GC può aiutare a individuare segnali di perdite o picchi di carico in momenti specifici.
7-6. Punti Chiave nella Messa a Punto di G1GC
G1GC è robusto di default, ma la messa a punto può renderlo ancora più stabile.
● Parametri Comuni
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=8m
-XX:InitiatingHeapOccupancyPercent=45
- MaxGCPauseMillis → Tempo di pausa target (es., 200 ms)
- G1HeapRegionSize → Dimensione della regione usata per partizionare l’heap
- InitiatingHeapOccupancyPercent → Percentuale di occupazione della generazione Old che innesca un ciclo GC
Tuttavia, nella maggior parte dei casi i valori predefiniti vanno bene, quindi modificateli solo quando avete una necessità chiara.
7-7. Riepilogo della Messa a Punto del GC
I miglioramenti del GC ti aiutano a visualizzare fattori che non sono evidenti semplicemente aumentando la dimensione dell’heap:
- Durata degli oggetti
- Modelli di utilizzo delle collezioni
- Se esiste una perdita di memoria
- Dove si concentra la pressione sull’heap
Ecco perché la messa a punto del GC è un processo estremamente importante per la mitigazione di “java heap space”.
8. Soluzione #4: Rilevare le Perdite di Memoria
Se l’errore persiste anche dopo aver aumentato l’heap e ottimizzato il codice, il sospetto più probabile è una perdita di memoria.
Spesso si pensa che Java sia resistente alle perdite di memoria perché esiste il GC, ma nella pratica le perdite di memoria sono una delle cause più problematiche e soggette a ricorrenza negli ambienti reali.
Qui ci concentriamo su passaggi pratici che puoi utilizzare subito, dalla comprensione delle perdite all’uso di strumenti di analisi come VisualVM e Eclipse MAT.
8-1. Che Cos’è una Perdita di Memoria? (Sì, Succede in Java)
Una perdita di memoria in Java è:
Uno stato in cui rimangono riferimenti a oggetti non necessari, impedendo al GC di recuperarli.
Anche con il garbage collection, le perdite si verificano comunemente quando:
- Gli oggetti sono mantenuti in campi
static - I listener registrati dinamicamente non vengono mai deregistrati
- Le collezioni continuano a crescere e mantengono riferimenti
- I valori ThreadLocal persistono inaspettatamente
- Il ciclo di vita del framework non corrisponde al ciclo di vita dei tuoi oggetti
Quindi le perdite sono assolutamente una possibilità normale.
8-2. Modelli Tipici di Perdite di Memoria
● ① Crescita delle Collezioni (Più Comune)
Aggiungere continuamente a List/Map/Set senza rimuovere le voci. Nei sistemi Java aziendali, una gran parte degli incidenti OOM proviene da questo schema.
● ② Mantenere Oggetti in Variabili statiche
private static List<User> cache = new ArrayList<>();
Questo spesso diventa il punto di partenza di una perdita.
● ③ Dimenticare di Deregistrare Listener / Callback
I riferimenti rimangono in background tramite GUI, osservatori, listener di eventi, ecc.
● ④ Uso Improprio di ThreadLocal
In ambienti con thread pool, i valori ThreadLocal possono persistere più a lungo del previsto.
● ⑤ Riferimenti Trattenuti da Librerie Esterne
Alcuna “memoria nascosta” è difficile da gestire dal codice dell’applicazione, rendendo essenziale l’analisi basata su tool.
8-3. Punti di Controllo per Rilevare i “Segni” di una Perdita di Memoria
Se vedi quanto segue, dovresti sospettare fortemente una perdita di memoria:
- Solo la generazione Old aumenta costantemente
- Full GC diventa più frequente
- La memoria diminuisce a malapena anche dopo Full GC
- L’uso dell’heap aumenta con il tempo di attività
- Solo la produzione si blocca dopo lunghi tempi di esecuzione
Questi sono molto più facili da capire una volta visualizzati con tool.
8-4. Tool #1: Controllo Visivo delle Perdite con VisualVM
VisualVM è spesso incluso nel JDK in alcune configurazioni ed è molto accessibile come primo tool.
● Cosa Puoi Fare con VisualVM
- Monitoraggio in tempo reale dell’uso della memoria
- Conferma della crescita della generazione Old
- Frequenza GC
- Monitoraggio dei thread
- Cattura di heap dump
● Come Catturare un Heap Dump
In VisualVM, apri la scheda “Monitor” e clicca sul pulsante “Heap Dump”.
Puoi quindi passare l’heap dump catturato direttamente a Eclipse MAT per un’analisi più approfondita.
8-5. Tool #2: Analisi Approfondita con Eclipse MAT (Memory Analyzer Tool)
Se c’è uno strumento standard del settore per l’analisi delle perdite di memoria in Java, è Eclipse MAT.
● Cosa Può Mostrarti MAT
- Quali oggetti consumano più memoria
- Quali percorsi di riferimento mantengono gli oggetti attivi
- Perché gli oggetti non vengono rilasciati
- Gonfiore delle collezioni
- Rapporti automatici “Leak Suspects”
● Passi Base per l’Analisi
- Apri l’heap dump (*.hprof)
- Esegui il “Leak Suspects Report”
- Trova le collezioni che trattengono grandi quantità di memoria
- Controlla l’Albero del Dominatore per identificare gli oggetti “parent”
- Segui il percorso di riferimento (“Path to GC Root”)
8-6. Se Capisci l’Albero del Dominatore, l’Analisi Accelera Drammaticamente
L’Albero del Dominatore ti aiuta a identificare gli oggetti che dominano (controllano) grandi porzioni dell’uso della memoria.
Esempi includono:
- Un
ArrayListmassiccio - Un
HashMapcon un numero enorme di chiavi - Una cache che non viene mai rilasciata
- Un singleton mantenuto da
static
Trovarli può ridurre drasticamente il tempo per localizzare la perdita.
8-7. Come Catturare un Heap Dump (da Riga di Comando)
Puoi anche catturare un heap dump usando jmap:
jmap -dump:format=b,file=heap.hprof <PID>
Puoi anche configurare la JVM per scaricare automaticamente l’heap quando si verifica OOM:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump.hprof
Questo è essenziale per le indagini sugli incidenti in produzione.
8-8. La Correzione Vera di una Perdita Richiede Modifiche al Codice
Se esiste una perdita, misure come:
- Aumentare la dimensione dell’heap
- Ottimizzare GC
sono meramente misure di supporto temporaneo.
In definitiva, hai bisogno di cambiamenti di design come:
- Correggere la parte che mantiene i riferimenti indefinitamente
- Rivedere il design delle collezioni
- Evitare l’uso eccessivo di
static - Implementare l’evizione e la pulizia della cache

8-9. Come Distinguere “Carenza di Heap” vs “Perdita di Memoria”
● In un Caso di Carenza di Heap
- OOM accade rapidamente con l’aumento del volume dei dati
- Scala con il carico di lavoro
- Aumentare l’heap stabilizza il sistema
● In un Caso di Perdita di Memoria
- OOM accade dopo un lungo tempo di attività
- Con l’aumento delle richieste, le prestazioni peggiorano gradualmente
- La memoria diminuisce a malapena anche dopo Full GC
- Aumentare l’heap non risolve il problema
8-10. Riassunto: Se l’Ottimizzazione dell’Heap Non Risolve OOM, Sospetta una Perdita
Tra i problemi di “java heap space”, la causa radice più consuma-tempo da identificare è spesso una perdita di memoria.
Ma con VisualVM + Eclipse MAT, è spesso possibile scoprirla in pochi minuti:
- Oggetti che consumano più memoria
- I riferimenti radice che li mantengono attivi
- La fonte del gonfiore delle collezioni
9. Problemi di “java heap space” in Docker / Kubernetes e Come Risolverli
Le applicazioni Java moderne vengono eseguite sempre più non solo su ambienti on-prem ma anche su Docker e Kubernetes (K8s).
Tuttavia, poiché gli ambienti container utilizzano un modello di memoria diverso rispetto all’host, ci sono molti punti facili da fraintendere per gli sviluppatori Java, e errori “java heap space” o OOMKilled (terminazione forzata del container) possono verificarsi frequentemente.
Questa sezione riassume la gestione della memoria specifica per i container e le impostazioni che devi conoscere nelle operazioni reali.
9-1. Perché gli Errori di Heap Space Sono Così Comuni nei Container
Il motivo è semplice:
Java potrebbe non riconoscere sempre correttamente i limiti di memoria del container.
● Un Fraintendimento Comune
“Poiché ho impostato un limite di memoria Docker --memory=512m, Java dovrebbe eseguire entro 512MB.”
→ In pratica, quell’assunzione può essere sbagliata.
Quando decide la dimensione dell’heap, Java potrebbe fare riferimento alla memoria fisica dell’host piuttosto che ai limiti del container.
Di conseguenza:
- Java decide “l’host ha molta memoria”
- Tenta di allocare un heap più grande
- Una volta superato il limite del container, il OOM Killer si attiva e il processo viene terminato forzatamente
9-2. Miglioramenti in Java 8u191+ e Java 11+
Da certi aggiornamenti di Java 8 e in Java 11+, è stato introdotto “UseContainerSupport”.
● Comportamento nei Container
- Può riconoscere i limiti basati su cgroup
- Calcola automaticamente la dimensione dell’heap entro quei limiti
Tuttavia, il comportamento varia ancora in base alla versione, quindi è consigliata una configurazione esplicita in produzione.
9-3. Impostazione Esplicita della Dimensione dell’Heap nei Container (Richiesta)
● Pattern di Avvio Raccomandato
docker run \
--memory=1g \
-e JAVA_OPTS="-Xms512m -Xmx800m" \
my-java-app
Punti chiave:
- Memoria del container: 1GB
- Heap Java: mantienilo entro 800MB
- Il resto è utilizzato da stack di thread e memoria nativa
● Esempio Cattivo (Molto Comune)
docker run --memory=1g my-java-app # no -Xmx
→ Java potrebbe allocare l’heap in base alla memoria dell’host, e una volta superato 1GB, si verifica OOMKilled.
9-4. Insidie nelle Impostazioni di Memoria in Kubernetes (K8s)
In Kubernetes, resources.limits.memory è critico.
● Esempio di Pod
resources:
limits:
memory: "1024Mi"
requests:
memory: "512Mi"
In questo caso, mantenere Java -Xmx intorno a 800MB-900MB è tipicamente più sicuro.
● Perché Impostarlo Più Basso del Limite?
Perché Java utilizza più dell’heap:
- Memoria nativa
- Stack di thread (centinaia di KB × numero di thread)
- Metaspace
- Overhead dei worker GC
- Codice compilato JIT
- Caricamento di librerie
Insieme, questi possono consumare facilmente 100–300MB.
In pratica, una regola comune è:
Se limit = X, imposta -Xmx a circa X × 0.7-0.8 per sicurezza.
9-5. Percentuale Automatica dell’Heap in Java 11+ (MaxRAMPercentage)
In Java 11, la dimensione dell’heap può essere calcolata automaticamente utilizzando regole come:
● Impostazioni Predefinite
-XX:MaxRAMPercentage=25
-XX:MinRAMPercentage=50
Significato:
- L’heap è limitato al 25% della memoria disponibile
- In ambienti piccoli, potrebbe utilizzare almeno 50% come heap
● Impostazione Raccomandata
Nei container, è spesso più sicuro impostare MaxRAMPercentage esplicitamente:
JAVA_OPTS="-XX:MaxRAMPercentage=70"
9-6. Perché OOMKilled Si Verifica Così Spesso nei Container (Pattern del Mondo Reale)
Un pattern comune in produzione:
- Limite di memoria K8s = 1GB
- Nessuna
-Xmxconfigurata - Java fa riferimento alla memoria dell’host e tenta di allocare più di 1GB di heap
- Il container viene terminato forzatamente → OOMKilled
Nota che questo non è necessariamente un evento java heap space (OutOfMemoryError)—si tratta di una terminazione OOM a livello di container.
9-7. Checkpoints Specifici per i Container Utilizzando Log GC e Metriche
Negli ambienti container, concentrati specialmente su:
- Se i riavvii del pod stanno aumentando
- Se gli eventi OOMKilled sono registrati
- Se la generazione Old continua a crescere
- Se il recupero GC diminuisce bruscamente in certi momenti
- Se la memoria nativa (non-heap) si sta esaurendo
Prometheus + Grafana rende molto più semplice la visualizzazione.
9-8. Riepilogo: le “Impostazioni Esplicite” sono le predefinite nei container
--memoryda solo potrebbe non far calcolare correttamente l’heap a Java- Imposta sempre
-Xmx - Lascia spazio di riserva per la memoria nativa e gli stack dei thread
- Imposta valori inferiori ai limiti di memoria di Kubernetes
- Su Java 11+, MaxRAMPercentage può essere utile
10. Anti‑Pattern da Evitare (Codice Errato / Impostazioni Errate)
L’errore “java heap space” si verifica non solo quando l’heap è realmente insufficiente, ma anche quando vengono utilizzati determinati modelli di codifica pericolosi o configurazioni errate.
Qui riassumiamo gli anti‑pattern comuni osservati frequentemente nel lavoro reale.
10-1. Lasciare Collezioni Illimitate Crescere All’infinito
Uno dei problemi più frequenti è il gonfiore delle collezioni.
● Esempio Errato: Aggiungere a una Lista Senza Limiti
List<String> logs = new ArrayList<>();
while (true) {
logs.add(getMessage()); // ← grows forever
}
Con un lungo tempo di attività, questo da solo può facilmente portarti a un OOM.
● Perché è Pericoloso
- Il GC non può recuperare memoria e la generazione Old si gonfia
- Il Full GC diventa frequente, rendendo l’app più incline a bloccarsi
- Copiare un numero enorme di oggetti aumenta il carico CPU
● Come Evitarlo
- Imposta un limite di dimensione (ad es., una cache LRU)
- Pulisci periodicamente
- Non conservare dati inutilmente
10-2. Caricare File o Dati Enormi Tutti in Una Volta
Questo è un errore comune nella elaborazione batch e lato server.
● Esempio Errato: Leggere un Enorme JSON in Un Solo Colpo
String json = Files.readString(Paths.get("large.json"));
Data d = mapper.readValue(json, Data.class);
● Cosa Va Storto
- Conservi sia la stringa pre‑parse che gli oggetti post‑parse in memoria
- Un file da 500 MB può consumare più del doppio in memoria
- Vengono creati oggetti intermedi aggiuntivi e l’heap si esaurisce
● Come Evitarlo
- Usa lo streaming (elaborazione sequenziale)
- Leggi a blocchi anziché caricare tutto in una volta
- Non conservare l’intero dataset in memoria
10-3. Continuare a Tenere Dati in Variabili statiche
● Esempio Errato
public class UserCache {
private static Map<String, User> cache = new HashMap<>();
}
● Perché è Pericoloso
staticvive finché la JVM è in esecuzione- Se usato come cache, le voci potrebbero non essere mai rilasciate
- I riferimenti rimangono, diventando terreno fertile per perdite di memoria
● Come Evitarlo
- Mantieni l’uso di
statical minimo - Usa un framework di cache dedicato (ad es., Caffeine)
- Imposta TTL e un limite di dimensione massimo
10-4. Uso Eccessivo di Stream / Lambda e Generazione di Enormi Liste Intermedie
L’API Stream è comoda, ma può creare oggetti intermedi internamente e mettere pressione sulla memoria.
● Esempio Errato (collect crea una massiccia lista intermedia)
List<Item> result = items.stream()
.map(this::convert)
.collect(Collectors.toList());
● Come Evitarlo
- Elabora sequenzialmente con un ciclo for
- Evita di generare liste intermedie non necessarie
- Se il dataset è grande, riconsidera l’uso di Stream in quella parte
10-5. Eseguire Massicce Concatenazioni di Stringhe con l’Operatore +
Poiché le Stringhe sono immutabili, ogni concatenazione crea un nuovo oggetto String.
● Esempio Errato
String result = "";
for (String s : list) {
result += s;
}
● Cosa è Sbagliato
- Una nuova String viene creata ad ogni iterazione
- Viene prodotto un numero enorme di istanze, esercitando pressione sulla memoria
● Come Evitarlo
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
10-6. Creare Troppe Cache e Non Gestirle
● Esempi Errati
- Memorizzare le risposte API in una Map indefinitamente
- Cacheare continuamente immagini o dati di file
- Nessun meccanismo di controllo come LRU
● Perché è Rischioso
- La cache cresce nel tempo
- La memoria non recuperabile aumenta
- Diventerà quasi sempre un problema in produzione
● Come Evitarlo
- Usa Caffeine / Guava Cache
- Imposta una dimensione massima
- Configura TTL (scadenza)
10-7. Mantenere Log o Statistiche in Memoria Continuamente
● Esempio Cattivo
List<String> debugLogs = new ArrayList<>();
debugLogs.add(message);
In produzione, i log dovrebbero essere scritti su file o sistemi di log. Mantenere in memoria è rischioso.
10-8. Non Specificare -Xmx in Container Docker
Questo rappresenta una grande porzione di incidenti moderni relativi all’heap.
● Esempio Cattivo
docker run --memory=1g my-app
● Cosa C’è di Sbagliato
- Java potrebbe ridimensionare automaticamente l’heap in base alla memoria host
- Una volta superato il limite del container, ottieni OOMKilled
● Come Evitarlo
docker run --memory=1g -e JAVA_OPTS="-Xmx700m"
10-9. Sovra-Tuning delle Impostazioni GC
Un tuning scorretto può ritorcersi contro.
● Esempio Cattivo
-XX:MaxGCPauseMillis=10
-XX:G1HeapRegionSize=1m
Parametri estremi possono rendere il GC eccessivamente aggressivo o impedirgli di stare al passo.
● Come Evitarlo
- Nella maggior parte dei casi, le impostazioni predefinite sono sufficienti
- Regola minimamente solo quando c’è un problema specifico e misurato
10-10. Riassunto: La Maggior Parte degli Anti-Patterns Deriva da “Memorizzare Troppo”
Quello che tutti questi anti-patterns hanno in comune è:
“Accumulare più oggetti del necessario.”
- Collezioni illimitate
- Ritenzione non necessaria
- Caricamento in blocco
- Design pesanti su static
- Cache fuori controllo
- Oggetti intermedi esplosivi
Evitare questi da soli può ridurre drasticamente gli errori “java heap space”.
11. Esempi Reali: Questo Codice È Pericoloso (Pattern Tipici di Problemi di Memoria)
Questa sezione introduce esempi di codice pericolosi frequentemente incontrati in progetti reali che spesso portano a errori “java heap space”, e spiega per ciascuno:
“Perché è pericoloso” e “Come risolverlo.”
In pratica, questi pattern si verificano spesso insieme, quindi questo capitolo è estremamente utile per le revisioni del codice e le indagini sugli incidenti.
11-1. Caricamento in Blocco di Dati Enormi
● Esempio Cattivo: Lettura di Tutte le Righe di un CSV Enorme
List<String> lines = Files.readAllLines(Paths.get("big.csv"));
● Perché È Pericoloso
- Più grande è il file, maggiore è la pressione sulla memoria
- Anche un CSV da 100MB può consumare più del doppio della memoria prima/dopo il parsing
- Mantenere record massicci può esaurire la generazione Old
● Miglioramento: Leggi via Stream (Elaborazione Sequenziale)
try (Stream<String> stream = Files.lines(Paths.get("big.csv"))) {
stream.forEach(line -> process(line));
}
→ Solo una riga è tenuta in memoria alla volta, rendendo questo molto sicuro.
11-2. Pattern di Gonfiamento della Collezione
● Esempio Cattivo: Accumulo Continuo di Oggetti Pesanti in una Lista
List<Order> orders = new ArrayList<>();
while (hasNext()) {
orders.add(fetchNextOrder());
}
● Perché È Pericoloso
- Ogni passo di crescita rialloca l’array interno
- Se non hai bisogno di tenere tutto, è puro spreco
- Tempi di esecuzione lunghi possono consumare spazio enorme nella generazione Old
● Miglioramento: Elabora Sequenzialmente + Batch Quando Necessario
while (hasNext()) {
Order order = fetchNextOrder();
process(order); // process without retaining
}
O batchalo:
List<Order> batch = new ArrayList<>(1000);
while (hasNext()) {
batch.add(fetchNextOrder());
if (batch.size() == 1000) {
processBatch(batch);
batch.clear();
}
}
11-3. Generare Troppi Oggetti Intermedi via Stream API
● Esempio Cattivo: Liste intermedie ripetute via map → filter → collect
List<Data> result = list.stream()
.map(this::convert)
.filter(d -> d.isValid())
.collect(Collectors.toList());
● Perché È Pericoloso
- Crea molti oggetti temporanei internamente
- Specialmente rischioso con liste enormi
- Più profonda è la pipeline, maggiore è il rischio
● Miglioramento: Usa un for-loop o elaborazione sequenziale
List<Data> result = new ArrayList<>();
for (Item item : list) {
Data d = convert(item);
if (d.isValid()) {
result.add(d);
}
}
11-4. Parsing di JSON o XML Tutto in Una Volta
● Esempio Cattivo
String json = Files.readString(Paths.get("large.json"));
Data data = mapper.readValue(json, Data.class);
● Perché È Pericoloso
- Sia la stringa JSON grezza che gli oggetti deserializzati rimangono in memoria
- Con file da 100MB, l’heap può riempirsi istantaneamente
- Problemi simili possono verificarsi anche usando API Stream, a seconda dell’uso
● Miglioramento: Usa un’API di Streaming
JsonFactory factory = new JsonFactory();
try (JsonParser parser = factory.createParser(new File("large.json"))) {
while (!parser.isClosed()) {
JsonToken token = parser.nextToken();
// Process only when needed and do not retain data
}
}
11-5. Caricamento di Tutte le Immagini / Dati Binari in Memoria
● Esempio Cattivo
byte[] image = Files.readAllBytes(Paths.get("large.png"));
● Perché È Pericoloso
- I dati binari possono essere grandi e “pesanti” per natura
- Nelle app di elaborazione immagini, questo è una delle principali cause di OOM
● Miglioramenti
- Usa buffering
- Elabora come stream senza trattenere l’intero file in memoria
- La lettura bulk di log multimilionari è similmente pericolosa
11-6. Ritenzione Infinita tramite Cache Statico
● Esempio Cattivo
private static final List<Session> sessions = new ArrayList<>();
● Cosa C’è di Sbagliato
sessionsnon verrà rilasciato fino all’uscita della JVM- Cresce con le connessioni e alla fine porta a OOM
● Miglioramenti
- Usa una cache gestita per dimensione (Caffeine, Guava Cache, ecc.)
- Gestisci chiaramente il ciclo di vita della sessione
11-7. Uso Sbagliato di ThreadLocal
● Esempio Cattivo
private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
ThreadLocal è utile, ma con i thread pool può mantenere i valori vivi e causare perdite di memoria.
● Miglioramenti
- Mantieni ThreadLocal a breve durata
- Evita di usarlo a meno che non sia veramente necessario
- Chiama
remove()per pulirlo
11-8. Creazione di Troppi Exception
Questo è spesso trascurato, ma le Exception sono oggetti molto pesanti a causa della generazione dello stack trace.
● Esempio Cattivo
for (...) {
try {
doSomething();
} catch (Exception e) {
// log only
}
}
→ L’inondazione di exception può mettere pressione sulla memoria.
● Miglioramenti
- Non usare exception per il flusso di controllo normale
- Rifiuta input non validi tramite validazione
- Evita di lanciare exception a meno che non sia necessario
11-9. Riassunto: Codice Pericoloso “Silenziosamente” Mangia il Tuo Heap
Il tema comune è:
“strutture che gradualmente stringono l’heap, sovrapposte l’una sull’altra.”
- Caricamento bulk
- Collezioni infinite
- Dimenticare di deregistrare/pulire
- Creazione di oggetti intermedi
- Inondazione di exception
- Ritenzione statica
- Avanzi di ThreadLocal
In tutti i casi, l’impatto diventa evidente durante esecuzioni lunghe.
12. Best Practices per la Gestione della Memoria in Java (Essenziali per Prevenire Ricorrenze)
Finora, abbiamo coperto le cause degli errori “java heap space” e contromisure come l’espansione dell’heap, miglioramenti del codice, tuning GC e indagine di perdite.
Questa sezione riassume best practices che prevengono affidabilmente le ricorrenze in operazioni reali.
Pensa a queste come alle regole minime per mantenere stabili le applicazioni Java.
12-1. Imposta la Dimensione dell’Heap Esplicitamente (Specialmente in Produzione)
Eseguire carichi di lavoro in produzione con i default è rischioso.
● Best Practices
- Imposta esplicitamente
-Xmse-Xmx - Non eseguire produzione con i default
- Mantieni dimensioni dell’heap consistenti tra dev e prod (evita differenze inaspettate)
Esempio:
-Xms1g -Xmx1g
In Docker / Kubernetes, devi impostare l’heap più piccolo per adattarlo ai limiti del container.
12-2. Monitora Appropriatamente (GC, Utilizzo Memoria, OOM)
I problemi dell’heap sono spesso prevenibili se catturi i segnali di allarme precoci.
.### ● Cosa monitorare
- Utilizzo della generazione vecchia
- Tendenze di crescita della generazione giovane
- Frequenza dei Full GC
- Tempo di pausa del GC
- Eventi OOMKilled del container
- Conteggio dei riavvii dei pod (K8s)
● Strumenti consigliati
- VisualVM
- JDK Mission Control
- Prometheus + Grafana
- Metriche del provider cloud (es., CloudWatch)
Un aumento graduale dell’utilizzo della memoria durante lunghi periodi di esecuzione è un classico segno di perdita.
12-3. Usa “Cache controllate”
Le cache fuori controllo sono una delle cause più comuni di OOM in produzione.
● Buone pratiche
- Usa Caffeine / Guava Cache
- Configura sempre TTL (scadenza)
- Imposta una dimensione massima (es., 1.000 voci)
- Evita le cache statiche il più possibile
Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build();
12-4. Fai attenzione all’uso eccessivo di Stream API e Lambda
Per grandi dataset, concatenare operazioni Stream aumenta gli oggetti intermedi.
● Buone pratiche
- Non concatenare map/filter/collect più del necessario
- Elabora grandi dataset sequenzialmente con un ciclo for
- Quando usi collect, sii consapevole del volume dei dati
Gli Stream sono comodi, ma non sono sempre amichevoli con la memoria.
12-5. Converti file / dati enormi in streaming
L’elaborazione in blocco è una delle principali cause di problemi di heap.
● Buone pratiche
- CSV →
Files.lines() - JSON → Jackson Streaming
- DB → paging
- API → chunked fetch (cursor/pagination)
Se imposti “non caricare tutto in memoria”, molti problemi di heap scompaiono.
12-6. Gestisci ThreadLocal con attenzione
ThreadLocal è potente, ma un uso improprio può causare gravi perdite di memoria.
● Buone pratiche
- Fai particolare attenzione quando è combinato con pool di thread
- Chiama
remove()dopo l’uso - Non memorizzare dati a lunga durata
- Evita ThreadLocal statici il più possibile
12-7. Cattura periodicamente i dump di heap per rilevare le perdite in anticipo
Per sistemi a lungo termine (app web, sistemi batch, IoT), catturare regolarmente i dump di heap e confrontarli aiuta a rilevare segnali di perdita precoce.
● Opzioni
- VisualVM
- jmap
-XX:+HeapDumpOnOutOfMemoryError-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heapdump.hprof
Il dump automatico in caso di OOM è una impostazione indispensabile in produzione.
12-8. Mantieni la sintonizzazione del GC al minimo
L’idea che “sintonizzare il GC aumenterà automaticamente le prestazioni” può essere pericolosa.
● Buone pratiche
- Inizia con le impostazioni predefinite
- Apporta modifiche minime solo quando esiste un problema misurato
- Usa G1GC come scelta predefinita
- In molti casi, aumentare l’heap è più efficace del micro‑tuning
12-9. Considera di suddividere l’architettura
Se i volumi di dati diventano troppo grandi o l’app diventa troppo monolitica e richiede un heap enorme, potresti aver bisogno di miglioramenti architetturali:
- Microservizi
- Suddivisione dell’elaborazione batch dei dati
- Disaccoppiamento con code di messaggi (Kafka, ecc.)
- Elaborazione distribuita (Spark, ecc.)
Se “non importa quanto heap aggiungi, non è mai abbastanza”, sospetta un problema architetturale.
12-10. Riepilogo: La gestione della memoria Java riguarda l’ottimizzazione a più livelli
I problemi di spazio heap raramente si risolvono con una singola impostazione o una singola correzione di codice.
● Punti chiave
- Imposta sempre l’heap esplicitamente
- Il monitoraggio è il più importante
- Non permettere mai il gonfiamento della collezione
- Usa lo streaming per grandi dati
- Gestisci correttamente le cache
- Usa ThreadLocal con attenzione
- Analizza le perdite con gli strumenti quando necessario
- I container richiedono una mentalità diversa
Seguire questi punti impedirà la maggior parte degli errori “java heap space” con alta certezza.
13. Riepilogo: Punti chiave per prevenire gli errori “java heap space”
In questo articolo, abbiamo trattato gli errori “java heap space” dalle cause radice alla mitigazione e alla prevenzione delle ricorrenze.
Qui raccogliamo gli elementi essenziali come un riepilogo pratico.
13-1. Il vero problema non è “Heap troppo piccolo” ma “Perché si esaurisce?”
“java heap space” non è solo una semplice carenza di memoria.
● La causa radice è tipicamente una delle seguenti
- Dimensione dell’heap troppo piccola (configurazione insufficiente)
- Elaborazione bulk di dati enormi (problema di design)
- Gonfiore delle collezioni (mancanza di cancellazione/design)
- Perdita di memoria (riferimenti rimasti)
- Miscalibrazione nei container (specifico per Docker/K8s)
Inizia con: “Perché l’heap è finito?”
13-2. Primi Passi per Indagare
① Confermare la dimensione dell’heap
→ Impostare esplicitamente -Xms / -Xmx
② Comprendere i vincoli di memoria a runtime
→ In Docker/Kubernetes, allineare i limiti e la dimensione dell’heap
→ Controllare anche -XX:MaxRAMPercentage
③ Catturare e ispezionare i log GC
→ La crescita della generazione old e i frequenti Full GC sono segnali di avvertimento
④ Catturare e analizzare i dump dell’heap
→ Usare VisualVM / MAT per raccogliere prove di perdite
13-3. Modelli ad Alto Rischio Comuni in Produzione
Come mostrato in tutto l’articolo, i seguenti pattern portano frequentemente a incidenti:
- Elaborazione bulk di file enormi
- Aggiungere a List/Map senza un limite
- Cache fuori controllo
- Accumulo di dati in static
- Oggetti intermedi esplosivi tramite catene di Stream
- Uso improprio di ThreadLocal
- Non impostare -Xmx in Docker
Se li vedi nel codice o nelle impostazioni, indaga prima.
13-4. Le Correzioni Fondamentali Riguardano il Design del Sistema e l’Elaborazione dei Dati
● Cosa rivedere a livello di sistema
- Passare la gestione di grandi dati a elaborazione streaming
- Usare cache con TTL, limiti di dimensione e eviction
- Eseguire monitoraggio regolare della memoria per applicazioni a lunga esecuzione
- Analizzare i primi segnali di perdite con gli strumenti
● Se è ancora difficile
- Separare l’elaborazione batch da quella online
- Microservizi
- Adottare piattaforme di elaborazione distribuita (Spark, Flink, ecc.)
Potrebbero essere necessari miglioramenti architetturali.
13-5. I Tre Messaggi Più Importanti
Se ricordi solo tre cose:
✔ Imposta sempre l’heap esplicitamente
✔ Non elaborare mai in bulk dati enormi
✔ Non puoi confermare le perdite senza dump dell’heap
Solo questi tre possono ridurre notevolmente gli incidenti critici in produzione causati da “java heap space”.
13-6. La Gestione della Memoria Java è una Competenza che Crea un Vero Vantaggio
La gestione della memoria Java può sembrare difficile, ma se la comprendi:
- L’investigazione degli incidenti diventa drasticamente più veloce
- I sistemi ad alto carico possono essere eseguiti stabilmente
- L’ottimizzazione delle prestazioni diventa più accurata
- Diventi un ingegnere che comprende sia l’applicazione sia l’infrastruttura
Non è un’esagerazione dire che la qualità del sistema è proporzionale alla comprensione della memoria.
14. FAQ
Infine, ecco una sezione pratica di Q&A che copre le domande comuni che le persone cercano su “java heap space”.
Questo completa l’articolo e aiuta a catturare un più ampio spettro di intenti degli utenti.
Q1. Qual è la differenza tra java.lang.OutOfMemoryError: Java heap space e GC overhead limit exceeded?
● java heap space
- Si verifica quando l’heap è fisicamente esaurito
- Spesso causato da dati enormi, gonfiore delle collezioni o impostazioni insufficienti
● GC overhead limit exceeded
- Il GC sta lavorando intensamente ma riconquista quasi nulla
- Un segno che il GC non può recuperare a causa di troppi oggetti vivi
- Spesso suggerisce una perdita di memoria o riferimenti persistenti
Un modello mentale utile:
heap space = limite già superato, GC overhead = subito prima del limite.
Q2. Se semplicemente aumento l’heap, sarà risolto?
✔ Può aiutare temporaneamente
✘ Non risolve la causa radice
- Se l’heap è davvero troppo piccolo per il tuo carico di lavoro → aiuta
- Se le collezioni o le perdite sono la causa → ricomparirà
Se la causa è una perdita, raddoppiare l’heap ritarda solo il prossimo OOM.
Q3. Quanto posso aumentare l’heap Java?
● Tipicamente: 50%–70% della memoria fisica
Perché devi riservare memoria per:
- Memoria nativa
- Stack dei thread
- Metaspace
- Lavoratori GC
- Processi OS
Specialmente in Docker/K8s, è pratica comune impostare:
-Xmx = 70%–80% del limite del container.
Q4. Perché Java riceve OOMKilled nei container (Docker/K8s)?
.### ● In molti casi, perché -Xmx non è impostato
Docker potrebbe non sempre passare correttamente i limiti del container a Java, quindi Java dimensiona l’heap in base alla memoria dell’host → supera il limite → OOMKilled.
✔ Correzione
docker run --memory=1g -e JAVA_OPTS="-Xmx800m"
Q5. C’è un modo semplice per capire se si tratta di una perdita di memoria?
✔ Se questi sono veri, è molto probabile una perdita
- L’utilizzo dell’heap continua a crescere con il tempo di attività
- La memoria diminuisce a malapena anche dopo un Full GC
- La generazione old cresce con un pattern a “gradini”
- L’OOM avviene dopo ore o giorni
- Le esecuzioni brevi sembrano a posto
Tuttavia, la conferma finale richiede l’analisi dei dump dell’heap (Eclipse MAT).
Q6. Le impostazioni dell’heap in Eclipse / IntelliJ non vengono applicate
● Cause comuni
- Non hai modificato la Configurazione di Esecuzione
- Le impostazioni predefinite dell’IDE hanno la precedenza
- Un altro script di avvio con
JAVA_OPTSsovrascrive le tue impostazioni - Hai dimenticato di riavviare il processo
Le impostazioni dell’IDE differiscono, quindi controlla sempre il campo “VM options” nella Configurazione di Esecuzione/Debug.
Q7. È vero che Spring Boot utilizza molta memoria?
Sì. Spring Boot spesso consuma più memoria a causa di:
- Configurazione automatica
- Molti Bean
- Caricamento delle classi in JAR pesanti
- Server web integrato (Tomcat, ecc.)
Rispetto a un semplice programma Java, può utilizzare circa 200–300 MB in più in alcuni casi.
Q8. Quale GC dovrei usare?
Nella maggior parte dei casi, G1GC è l’impostazione predefinita più sicura.
● Raccomandazioni in base al carico di lavoro
- App web → G1GC
- Job batch ad alta produttività → Parallel GC
- Esigenze di latenza ultra-bassa → ZGC / Shenandoah
Senza una ragione forte, scegli G1GC.
Q9. Come dovrei gestire l’heap negli ambienti serverless (Cloud Run / Lambda)?
Gli ambienti serverless hanno limiti di memoria rigidi, quindi dovresti configurare esplicitamente l’heap.
Esempio (Java 11):
-XX:MaxRAMPercentage=70
Nota anche che la memoria può aumentare durante i cold start, quindi lascia margine nella tua configurazione dell’heap.
Q10. Come posso prevenire il ripetersi dei problemi di heap Java?
Se segui rigorosamente queste tre regole, la ricorrenza diminuisce drasticamente:
✔ Imposta l’heap esplicitamente
✔ Elabora grandi quantità di dati tramite streaming
✔ Rivedi regolarmente i log GC e i dump dell’heap
Riepilogo: Usa le FAQ per Eliminare i Dubbi e Applicare Contromisure Pratiche sulla Memoria
Questa FAQ ha coperto le domande comuni basate su ricerche su “java heap space” con risposte pratiche.
Insieme all’articolo principale, dovrebbe aiutarti a diventare esperto nella gestione dei problemi di memoria Java e a migliorare significativamente la stabilità del sistema in produzione.


