Debug di memory leak in Java Spring Boot: traccia heap dump

Debug di memory leak in Java (Spring Boot): come tracciare heap dump e GC logs in produzione

Introduzione

Se sei un ingegnere backend o un SRE che gestisce applicazioni Spring Boot, probabilmente hai incontrato almeno una volta un memory leak in produzione. Un memory leak può trasformare un servizio stabile in un collo di bottiglia, aumentare il tempo medio di risoluzione (MTTR) e mettere a rischio gli SLA. In questa guida per principianti, spiegheremo passo‑passo come eseguire il debug di memory leak in Java in un contesto Spring Boot, dalla configurazione della JVM alla raccolta e all'analisi di heap dump e log del garbage collector (GC). Alla fine avrai un risultato concreto: un heap dump scaricato e i log GC pronti per l'analisi.


Cos'è un memory leak in Java

Un memory leak in Java si verifica quando gli oggetti non più necessari rimangono referenziati, impedendo alla JVM di liberarli durante il garbage collection.

Definizione e sintomi

  • Definizione: gli oggetti continuano a occupare memoria anche se non sono più utilizzati.
  • Sintomi comuni: aumento costante dell'uso di heap, frequenti Full GC, rallentamento delle richieste, out‑of‑memory error.

Impatto su SLA e MTTR

Un memory leak non controllato può far superare i limiti di SLA (Service Level Agreement) e aumentare drasticamente il MTTR, perché il team deve prima individuare la causa prima di poter intervenire.


Preparare l'ambiente di produzione per il debug

Configurare JVM per heap dump

  1. Aggiungi le seguenti opzioni al file di avvio della tua applicazione Spring Boot:
    -XX:+HeapDumpOnOutOfMemoryError 
    -XX:HeapDumpPath=/var/log/app/heapdump.hprof
    
  2. Verifica che la directory di destinazione sia scrivibile dal processo.
  3. Riavvia l'applicazione per applicare le modifiche.

Abilitare i log del garbage collector

Per ottenere informazioni dettagliate sul comportamento del GC, aggiungi:

-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintGCApplicationStoppedTime 
-XX:+PrintGCApplicationConcurrentTime 
-XX:+PrintGCLogFile=/var/log/app/gc.log

Queste impostazioni genereranno un file gc.log con timestamp, durata delle pause e metriche di memoria.


Raccogliere e analizzare l'heap dump

Generare l'heap dump

Se il leak non provoca un OutOfMemoryError, puoi forzare un dump con il comando JDK:

jcmd <pid> GC.heap_dump /var/log/app/manual_dump.hprof

Sostituisci <pid> con l'ID del processo Java.

Strumenti di analisi (Eclipse MAT, VisualVM)

  • Eclipse MAT: importa il file .hprof, usa la vista Dominators per identificare gli oggetti più grandi.
  • VisualVM: collega al processo live, cattura un heap dump e usa la sezione Classes per vedere il numero di istanze.

Snippet per featured snippet: Per tracciare un heap dump in produzione, abilita -XX:+HeapDumpOnOutOfMemoryError, specifica -XX:HeapDumpPath, e usa jcmd <pid> GC.heap_dump <path> per generare manualmente il dump.


Interpretare i log GC

Principali metriche GC

  • Pause Time: tempo totale di pausa del GC.
  • Throughput: percentuale di tempo in cui la JVM è attiva (obiettivo > 99%).
  • Heap Utilization: percentuale di heap usata prima e dopo il GC.

Identificare pattern di perdita

Cerca nei log sequenze di Full GC con durata crescente. Un pattern tipico è:

2023-07-15T12:34:56.789+0000: [Full GC (Allocation Failure)  2048M->1024M(4096M), 0.4567890 secs]

Se la memoria libera non ritorna a valori bassi, probabilmente c'è un leak.


Passi pratici per risolvere il leak

Come procedere: una volta individuato l'oggetto responsabile, analizza il codice che lo crea e verifica se è possibile rilasciare la referenza.

Individuare la causa

  1. Usa la vista Dominators di Eclipse MAT per trovare il root che trattiene la maggior parte della memoria.
  2. Analizza il stack trace associato a quell'oggetto per capire da quale classe proviene.
  3. Verifica se la classe implementa correttamente close() o dispose().

Applicare correzioni e verificare

  • Correzione: aggiungi una chiamata a close() o rimuovi la referenza non più necessaria.
  • Verifica: riavvia l'applicazione in un ambiente di staging, ripeti il test di carico e controlla che le metriche GC tornino a valori normali.

Checklist rapida

  • JVM configurata per heap dump automatico.
  • Log GC abilitati con timestamp.
  • Heap dump analizzato con Eclipse MAT.
  • Pattern di Full GC monitorati.
  • Codice corretto per rilasciare risorse.

Come Lescopr può semplificare il debug

Lescopr fornisce un'integrazione nativa con le JVM Java, consentendo di:

  • Raccogliere heap dump e log GC in modo automatizzato.
  • Visualizzare metriche di memoria e GC direttamente su dashboard APM.
  • Impostare alert basati su soglie di heap usage e durata delle pause GC.

Per approfondire, la documentazione di Lescopr descrive la configurazione passo dopo passo.