Garbage Collection in Java

La raccolta dei rifiuti in Java è uno degli argomenti avanzati. La conoscenza della GC in Java ci aiuta a ottimizzare le prestazioni del nostro programma in esecuzione.

Raccolta dei rifiuti in Java

  • In Java, i programmatori non devono preoccuparsi di distruggere gli oggetti che non sono più in uso. Il Garbage Collector si occupa di ciò.
  • Il Garbage Collector è un thread daemon che continua ad essere eseguito in background. Fondamentalmente, libera la memoria dell’heap distruggendo gli oggetti irraggiungibili.
  • Gli oggetti irraggiungibili sono quelli a cui nessuna parte del programma fa più riferimento.
  • Possiamo scegliere il garbage collector per il nostro programma Java attraverso le opzioni JVM, vedremo queste opzioni nella sezione successiva di questo tutorial.

Come funziona la raccolta automatica dei rifiuti?

La raccolta automatica dei rifiuti è un processo che guarda alla memoria Heap, identifica (anche conosciuta come “marcatura”) gli oggetti irraggiungibili e li distrugge con la compattazione. Un problema di questo approccio è che, man mano che il numero di oggetti aumenta, il tempo di Garbage Collection continua ad aumentare, poiché deve attraversare l’intera lista di oggetti alla ricerca dell’oggetto irraggiungibile. Tuttavia, l’analisi empirica delle applicazioni mostra che la maggior parte degli oggetti ha una durata breve. Questo comportamento è stato utilizzato per migliorare le prestazioni di JVM, e la metodologia adottata è comunemente chiamata Garbage Collection generazionale. In questo metodo, lo spazio Heap è diviso in generazioni come la Generazione Giovane, la Generazione Vecchia o Tenured e la Generazione Permanente. Lo spazio di heap della generazione giovane è il nuovo spazio in cui vengono creati tutti gli oggetti nuovi. Una volta riempito, si verifica una raccolta dei rifiuti minore (anche nota come GC minore). Ciò significa che tutti gli oggetti morti da questa generazione vengono distrutti. Questo processo è veloce perché, come possiamo vedere dal grafico, la maggior parte di essi sarebbe morta. Gli oggetti sopravvissuti nella generazione giovane invecchiano e alla fine si spostano nelle generazioni più vecchie. La Generazione Vecchia viene utilizzata per memorizzare oggetti a lunga durata. Tipicamente, viene impostata una soglia per l’oggetto della generazione giovane e quando viene raggiunta quell’età, l’oggetto viene spostato nella generazione vecchia. Alla fine, la generazione vecchia deve essere raccolta. Questo evento è chiamato una GC Maggiore (raccolta maggiore dei rifiuti). Spesso è molto più lento perché coinvolge tutti gli oggetti vivi. Inoltre, c’è il Full GC, che significa pulire l’intero Heap – sia gli spazi della generazione giovane che della generazione più vecchia. Infine, fino a Java 7, c’era una Generazione Permanente (o Perm Gen), che conteneva metadati richiesti da JVM per descrivere le classi e i metodi utilizzati nell’applicazione. È stato rimosso in Java 8.

Java Garbage Collectors

Il JVM fornisce effettivamente quattro diversi garbage collector, tutti generazionali. Ognuno ha i propri vantaggi e svantaggi. La scelta del garbage collector da utilizzare è a nostra discrezione e può esserci una differenza drammatica nella velocità di esecuzione e nelle pause dell’applicazione. Tutti questi suddividono l’heap gestito in segmenti diversi, utilizzando l’antico presupposto che la maggior parte degli oggetti nell’heap ha una breve durata e dovrebbe essere riciclata rapidamente. Quindi, i quattro tipi di garbage collector sono:

Serial GC

Questo è il garbage collector più semplice, progettato per sistemi single-threaded e dimensioni di heap ridotte. Blocca tutte le applicazioni durante il funzionamento. Può essere attivato utilizzando l’opzione del JVM -XX:+UseSerialGC

Parallel/Throughput GC

Questo è il raccoglitore predefinito di JVM in JDK 8. Come suggerisce il nome, utilizza thread multipli per scansionare lo spazio di heap e eseguire la compattazione. Uno svantaggio di questo raccoglitore è che mette in pausa i thread dell’applicazione durante l’esecuzione di una minore o una completa GC. È più indicato per applicazioni che possono gestire tali pause e cercano di ottimizzare l’overhead della CPU causato dal raccoglitore.

Il raccoglitore CMS

L’algoritmo del raccoglitore CMS (“concurrent-mark-sweep”) utilizza thread multipli (“concurrent”) per scansionare l’heap (“mark”) alla ricerca di oggetti non utilizzati che possono essere riciclati (“sweep”). Questo raccoglitore entra in modalità Stop-The-World (STW) in due casi: – Durante l’inizializzazione della marcatura iniziale delle radici, ovvero degli oggetti nella generazione vecchia che sono raggiungibili dai punti di ingresso del thread o dalle variabili statiche – Quando l’applicazione ha modificato lo stato dell’heap mentre l’algoritmo era in esecuzione in modo concorrente e lo ha costretto a tornare indietro per effettuare alcune operazioni finali per assicurarsi di avere gli oggetti giusti marcati. Questo raccoglitore potrebbe incontrare problemi di promozione. Se alcuni oggetti della generazione giovane devono essere spostati nella generazione vecchia e il raccoglitore non ha avuto abbastanza tempo per fare spazio nello spazio della generazione vecchia, si verificherà un fallimento della promozione. Per prevenirlo, possiamo fornire una dimensione maggiore dell’heap per la generazione vecchia o fornire più thread in background al raccoglitore.

Collezionista G1

Ultimo ma non meno importante è il collezionista Garbage-First, progettato per dimensioni di heap superiori a 4GB. Divide la dimensione dell’heap in regioni che vanno da 1MB a 32MB, in base alla dimensione dell’heap. Viene eseguita una fase di marcatura globale concorrente per determinare la vivacità degli oggetti in tutto l’heap. Dopo che la fase di marcatura è completa, G1 sa quali regioni sono per lo più vuote. Raccoglie prima gli oggetti non raggiungibili da queste regioni, il che di solito produce una grande quantità di spazio libero. Quindi G1 raccoglie queste regioni (contenenti spazzatura) per prime, e quindi il nome Garbage-First. G1 utilizza anche un modello di previsione della pausa per raggiungere un obiettivo di tempo di pausa definito dall’utente. Seleziona il numero di regioni da raccogliere in base all’obiettivo di tempo di pausa specificato. Il ciclo di raccolta dei rifiuti G1 include le fasi mostrate nella figura:

  1. Fase solo giovane: questa fase include solo gli oggetti della generazione giovane e li promuove alla generazione vecchia. La transizione tra la fase solo giovane e la fase di recupero dello spazio inizia quando la generazione vecchia è occupata fino a una certa soglia, cioè la soglia di occupazione iniziale dell’heap. In questo momento, G1 pianifica una raccolta iniziale giovane solo anziché una raccolta giovane regolare.

  2. Marcatura Iniziale: Questo tipo di raccolta avvia il processo di marcatura oltre a una normale raccolta giovane. La marcatura concorrente determina tutti gli oggetti attualmente vivi nelle regioni della generazione vecchia da conservare per la successiva fase di recupero dello spazio. Mentre la marcatura non è completamente terminata, possono verificarsi raccolte giovani regolari. La marcatura si conclude con due pause speciali che fermano il mondo: Remark e Cleanup.

  3. Remark: Questa pausa finalizza la marcatura stessa e esegue l’elaborazione globale dei riferimenti e lo scarico delle classi. Tra Remark e Cleanup, G1 calcola un riepilogo delle informazioni sulla vitalità in modo concorrente, che verrà finalizzato e utilizzato nella pausa Cleanup per aggiornare le strutture dati interne.

  4. Cleanup: Questa pausa prende anche le regioni completamente vuote e determina se seguirà effettivamente una fase di reclamo dello spazio. Se segue una fase di reclamo dello spazio, la fase solo giovane si completa con una singola raccolta solo giovane.

  5. Fase di reclamo dello spazio: Questa fase consiste in diverse raccolte miste – oltre alle regioni della generazione giovane, evacua anche gli oggetti vivi delle regioni della generazione vecchia. La fase di reclamo dello spazio termina quando G1 determina che evacuare ulteriori regioni della generazione vecchia non porterebbe abbastanza spazio libero che ne valga la pena.

G1 può essere abilitato utilizzando il flag -XX:+UseG1GC. Questa strategia riduce le possibilità che l’heap si esaurisca prima che i thread di background abbiano finito la scansione degli oggetti irraggiungibili. Inoltre, compatta l’heap durante l’esecuzione, cosa che il raccoglitore CMS può fare solo in modalità STW. In Java 8 è stata introdotta un’ottimizzazione chiamata string deduplication per il raccoglitore G1. Come sappiamo, gli array di caratteri che rappresentano le nostre stringhe occupano una grande quantità di spazio nell’heap. È stata introdotta una nuova ottimizzazione che consente al raccoglitore G1 di identificare le stringhe duplicate presenti nell’heap e modificarle in modo che puntino allo stesso array di caratteri interno, evitando così copie multiple delle stesse stringhe nell’heap in modo non necessario. Possiamo abilitare questa ottimizzazione utilizzando l’argomento JVM -XX:+UseStringDeduplication. G1 è il raccoglitore di default in JDK 9.

Java 8 PermGen e Metaspace

Come già accennato, lo spazio della Generazione Permanente è stato rimosso a partire da Java 8. Quindi, ora, l’HotSpot JVM di JDK 8 utilizza la memoria nativa per la rappresentazione dei metadati di classe, chiamata Metaspace. La maggior parte delle allocazioni per i metadati di classe avviene nella memoria nativa. Inoltre, c’è un nuovo flag MaxMetaspaceSize, per limitare la quantità di memoria utilizzata per i metadati di classe. Se non specifichiamo il valore per questo, il Metaspace si ridimensiona durante l’esecuzione in base alla domanda dell’applicazione in esecuzione. La raccolta dei rifiuti del Metaspace viene attivata quando l’utilizzo dei metadati di classe raggiunge il limite di MaxMetaspaceSize. La raccolta eccessiva dei rifiuti del Metaspace può essere un sintomo di perdita di memoria delle classi, dei classloader o di dimensioni inadeguate per la nostra applicazione. Questo è tutto per la Garbage Collection in Java. Spero tu abbia capito i diversi garbage collector che abbiamo in Java. Riferimenti: Documentazione Oracle, G1 GC.

Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java