Sette Tipi di Garbage Collector JAVA

Questo documento spiegherà cos’è il garbage collector in java e i principali tipi di garbage collector con i comportamenti di ogni garbage collector. Inoltre si prega di notare che in questo articolo non sto spiegando come avviene l’allocazione dell’heap e l’avvio dei garbage collector maggiori/minori. Sarà spiegato nel prossimo articolo.

Java è un linguaggio di programmazione orientato agli oggetti che include il Garbage Collection automatico. Java alloca e dealloca automaticamente la memoria in modo che i programmi non siano gravati da questo compito. Ora (Java-12), Java ha sette tipi di garbage collector,

  1. Serial Garbage Collector
  2. Parallel Garbage Collector
  3. CMS Garbage Collector
  4. G1 Garbage Collector
  5. Epsilon Garbage Collector
  6. Z garbage collector
  7. Shenandoah Garbage Collector

5° e 6° garbage collector sono stati introdotti in java 11 e 7° in java 12. Nella maggior parte delle applicazioni in produzione ha usato i primi quattro tipi di garbage collector. Poiché gli ultimi tre garbage collector sono entrati in scena di recente.

Dobbiamo preoccuparci dei GC?

Sì, infatti. Dobbiamo preoccuparci del GC e dei suoi comportamenti. Perché può fornire una significativa differenza di prestazioni. Ogni GC ha i suoi vantaggi e svantaggi. Come sviluppatori, dobbiamo avere un’idea chiara dei comportamenti di tutti i garbage collector e dobbiamo selezionare un garbage collector in base al nostro scenario aziendale. Possiamo scegliere un garbage collector passando la scelta come argomento della JVM.

Serial Garbage Collector

Questa è l’implementazione GC più semplice. È fondamentalmente progettata per un ambiente a thread singolo. Questa implementazione GC congela tutti i thread dell’applicazione quando viene eseguita. Utilizza un singolo thread per la garbage collection. Quindi, non è una buona idea usarlo in applicazioni multi-thread come gli ambienti server.

Per abilitare il Serial Garbage Collector, possiamo usare il seguente argomento:

java -XX:+UseSerialGC -jar Application.java

Parallel Garbage Collector

Il parallel garbage collector è anche chiamato come throughput collector. A differenza del garbage collector seriale, questo utilizza più thread per la garbage collection. Simile al garbage collector seriale, questo congela anche tutti i thread dell’applicazione mentre esegue la garbage collection. Il garbage collector è adatto meglio per quelle applicazioni che possono sopportare le pause dell’applicazione.

Per abilitare il Garbage Collector parallelo, possiamo usare il seguente argomento:

java -XX:+UseParallelGC -jar Application.java

Se usiamo questo GC, possiamo specificare il numero massimo di thread di garbage collection e il tempo di pausa, il throughput e l’impronta (dimensione dell’heap)

Il numero di thread del garbage collector può essere controllato con l’opzione della riga di comando

-XX:ParallelGCThreads=<N>

L’obiettivo massimo del tempo di pausa (intervallo tra due GC) è specificato con l’opzione della riga di comando

-XX:MaxGCPauseMillis=<N>

L’obiettivo massimo di throughput (misurato rispetto al tempo speso a fare garbage collection rispetto al tempo speso al di fuori della garbage collection) è specificato dall’opzione della riga di comando

-XX:GCTimeRatio=<N>

Un’impronta massima di heap (la quantità di memoria heap che un programma richiede durante l’esecuzione) è specificata usando l’opzione -Xmx<N>.

CMS Garbage Collector

Il garbage collector CMS (Concurrent Mark Sweep) utilizza più thread per la raccolta dei rifiuti. Esamina la memoria dell’heap per marcare le istanze per l’eliminazione e poi spazza le istanze marcate. È progettato per applicazioni che preferiscono pause di garbage collection più brevi e che possono permettersi di condividere le risorse del processore con il garbage collector mentre l’applicazione è in esecuzione.

Il garbage collector CMS trattiene tutti i thread dell’applicazione solo nei seguenti due scenari

  1. Durante la marcatura degli oggetti referenziati nello spazio della vecchia generazione.
  2. Ogni cambiamento nella memoria heap in parallelo con l’esecuzione della garbage collection

In confronto al garbage collector parallelo, il collettore CMS usa più CPU per garantire un migliore throughput dell’applicazione. Se possiamo allocare più CPU per una migliore prestazione, allora il CMS garbage collector è la scelta preferita rispetto al collettore parallelo.

Per abilitare il CMS Garbage Collector, possiamo usare il seguente argomento:

java -XX:+USeParNewGC -jar Application.java

G1 Garbage Collector

G1 (Garbage First) Garbage Collector è progettato per applicazioni in esecuzione su macchine multiprocessore con grande spazio di memoria. È disponibile da JDK7 Update 4 e nelle versioni successive.

Si separa la memoria heap in regioni e fa la raccolta in esse in parallelo. G1 compatta anche lo spazio heap libero in movimento subito dopo aver recuperato la memoria. Ma il garbage collector CMS compatta la memoria in situazioni di arresto del mondo (STW). Il collettore G1 sostituirà il collettore CMS poiché è più efficiente nelle prestazioni.

Nel collettore G1 ci sono due fasi;

  1. Marking
  2. Sweeping

A differenza degli altri collettori, il collettore G1 partiziona l’heap in un insieme di regioni heap di uguali dimensioni, ciascuna un intervallo contiguo di memoria virtuale. Quando esegue le garbage collection, G1 mostra una fase di marcatura globale simultanea per determinare la liveness degli oggetti in tutto l’heap.

Dopo che la fase di marcatura è completata, G1 sa quali regioni sono per lo più vuote. Raccoglie prima in queste aree, il che di solito produce una quantità significativa di spazio libero. È per questo che questo metodo di garbage collection è chiamato Garbage-First.

Per abilitare G1 Garbage Collector, possiamo usare il seguente argomento:

java -XX:+UseG1GC -jar Application.java

Epsilon Garbage Collector

Epsilon è un garbage collector non operativo o passivo. Alloca la memoria per l’applicazione, ma non raccoglie gli oggetti inutilizzati. Quando l’applicazione esaurisce l’heap di Java, la JVM si spegne. Significa che Epsilon garbage collector permette alle applicazioni di esaurire la memoria e andare in crash.

Lo scopo di questo garbage collector è misurare e gestire le prestazioni delle applicazioni. I garbage collector attivi sono programmi complessi che vengono eseguiti all’interno della JVM insieme alla tua applicazione. Epsilon rimuove l’impatto che il GC ha sulle prestazioni. Non ci sono cicli GC o barriere di lettura o scrittura. Quando si usa Epsilon GC, il codice viene eseguito in isolamento. Epsilon aiuta a visualizzare come la garbage collection influisce sulle prestazioni dell’app e qual è la soglia di memoria, dato che mostrerà quando si esaurisce. Per esempio, se pensiamo di aver bisogno di un solo gigabyte di memoria per la nostra applicazione, possiamo eseguirla con -Xmx1g e vedere il comportamento. Se quell’allocazione di memoria non è sufficiente, rilanciatelo con un heap dump. Notate che dobbiamo abilitare questa opzione per ottenere un heap dump. Possiamo usare questo argomento per ottenere un heap dump quando l’applicazione va in crash a causa dell’esaurimento della memoria.

XX:HeapDumpOnOutOfMemoryError

Se abbiamo bisogno di spremere ogni bit di performance dalla nostra applicazione, Epsilon potrebbe essere la vostra migliore opzione per un GC. Ma dobbiamo avere una comprensione completa di come il nostro codice usa la memoria. Se non crea quasi nessuna spazzatura o si sa esattamente quanta memoria usa per il periodo in cui viene eseguito, Epsilon è una valida opzione.

Per abilitare Epsilon Garbage Collector, possiamo usare il seguente argomento:

java -XX:+UseEpsilonGC -jar Application.java

Z garbage collector

ZGC esegue tutto il lavoro costoso simultaneamente, senza fermare l’esecuzione dei thread dell’applicazione per più di 10ms, il che lo rende adatto alle applicazioni che richiedono bassa latenza e/o usano un heap molto grande. Secondo la documentazione Oracle, può gestire heap multi-terabyte. Oracle ha introdotto ZGC in Java 11. Il garbage collector Z esegue i suoi cicli nei suoi thread. Mette in pausa l’applicazione per una media di 1 ms. I collettori G1 e Parallel hanno una media di circa 200 ms.

In Java 12, Oracle ha aggiunto correzioni delle prestazioni e lo scarico delle classi anche se Z è ancora in stato sperimentale. È disponibile solo su Linux a 64 bit. Ma ZGC sfrutta i puntatori a 64 bit con una tecnica chiamata colorazione dei puntatori. I puntatori colorati memorizzano informazioni extra sugli oggetti nell’heap. Questo è uno dei motivi per cui è limitato alla JVM a 64 bit. Questo articolo ha spiegato profondamente questo scenario (https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/).

ZGC fa la sua marcatura in tre fasi.

1. Breve fase di stop-the-world – Esamina le radici GC, variabili locali che puntano al resto dell’heap. Il numero totale di queste radici è solitamente minimo e non scala con la dimensione del carico, quindi le pause di ZGC sono molto brevi e non aumentano con la crescita dell’heap.

2. Fase concorrente – Cammina sul grafico degli oggetti ed esamina i puntatori colorati, marcando gli oggetti accessibili. La barriera di carico impedisce la contesa tra la fase GC e qualsiasi attività dell’applicazione.

3. Fase di rilocazione – Sposta gli oggetti vivi per liberare grandi sezioni dell’heap e rendere le allocazioni più veloci. Quando inizia la fase di rilocazione, ZGC divide l’heap in pagine e lavora su una pagina alla volta. Una volta che ZGC finisce di spostare qualsiasi radice, il resto della rilocazione avviene in una fase concomitante.

ZGC cercherà di impostare da solo il numero di thread, e di solito ha ragione. Ma se ZGC ha troppi thread, farà morire di fame la vostra applicazione. Se non ne ha abbastanza, creerete spazzatura più velocemente di quanto il GC possa raccoglierla. Le fasi di ZGC illustrano come gestisce grandi heap senza impattare sulle prestazioni quando la memoria dell’applicazione cresce.

Per abilitare Z Garbage Collector, possiamo usare il seguente argomento:

java -XX:+UseZGC -jar Application.java

Shenandoah

Shenandoah è un garbage collector a tempo di pausa ultra-basso che riduce i tempi di pausa del GC eseguendo più lavoro di garbage collection contemporaneamente al programma Java in esecuzione. CMS e G1 eseguono entrambi la marcatura concorrente degli oggetti vivi. Shenandoah aggiunge la compattazione concorrente.

Shenandoah usa regioni di memoria per gestire quali oggetti non sono più in uso e quali sono vivi e pronti per la compressione. Shenandoah aggiunge anche un puntatore di inoltro ad ogni oggetto heap e lo usa per controllare l’accesso all’oggetto. Il design di Shenandoah scambia i cicli di CPU e lo spazio per migliorare il tempo di pausa. Il puntatore di inoltro rende facile spostare gli oggetti, ma gli spostamenti aggressivi significano che Shenandoah usa più memoria e richiede più lavoro parallelo di altri GC. Ma fa il lavoro extra con brevissime pause di stop-the-world.

Shenandoah elabora l’heap in molte piccole fasi, la maggior parte delle quali sono contemporanee all’applicazione. Questo design rende possibile al GC di gestire un grande heap in modo efficiente.

  1. Prima pausa stop-the-world nel ciclo. Prepara l’heap per la marcatura concorrente e scansiona l’insieme delle radici. Come ZGC, la lunghezza di questa pausa corrisponde alla dimensione del root set, non dell’heap.
  2. In seguito, una fase concomitante percorre l’heap e identifica gli oggetti raggiungibili e non raggiungibili.
  3. La terza termina il processo di marcatura svuotando gli aggiornamenti pendenti dell’heap e scansionando nuovamente il root set. Questa fase innesca la seconda pausa di stop-the-world nel ciclo. Il numero di aggiornamenti in sospeso e la dimensione del root set determinano la durata della pausa.
  4. Poi, un’altra fase concomitante copia gli oggetti fuori dalle regioni identificate nella fase finale di marcatura. Questo processo distingue Shenandoah dagli altri GC poiché compatta aggressivamente l’heap in parallelo con i thread dell’applicazione.
  5. La fase successiva attiva la terza (e più breve) pausa del ciclo. Si assicura che tutti i thread del GC abbiano finito l’evacuazione.
  6. Quando finisce, una fase concomitante percorre l’heap e aggiorna i riferimenti agli oggetti spostati in precedenza nel ciclo.
  7. L’ultima pausa del ciclo termina l’aggiornamento dei riferimenti aggiornando il root set. Allo stesso tempo, ricicla le regioni evacuate.
  8. Finalmente, l’ultima fase recupera le regioni evacuate, che ora non hanno riferimenti in esse.

Possiamo configurare Shenandoah con una delle tre euristiche. Esse governano quando il GC inizia i suoi cicli e come seleziona le regioni da evacuare.

1. Adattiva: Osserva i cicli del GC e inizia il ciclo successivo in modo che si completi prima che l’applicazione esaurisca l’heap. Questa euristica è la modalità predefinita.

2. Statica: Avvia un ciclo GC basato sull’occupazione dell’heap e sulla pressione di allocazione.

3. Compact: Esegue cicli GC continuamente. Shenandoah inizia un nuovo ciclo non appena il precedente finisce o in base alla quantità di heap allocata dall’ultimo ciclo. Questa euristica incorre in un overhead di throughput ma fornisce il miglior recupero di spazio.

Shenandoah ha bisogno di raccogliere l’heap più velocemente di quanto l’applicazione che serve lo alloca. Se la pressione di allocazione è troppo alta e non c’è abbastanza spazio per nuove allocazioni, ci sarà un fallimento. Shenandoah ha dei meccanismi configurabili per questa situazione.

  • Pacing: Se Shenandoah inizia a rimanere indietro rispetto al tasso di allocazione, metterà in stallo i thread di allocazione per recuperare il ritardo. Gli stalli sono di solito sufficienti per lievi picchi di allocazione. Shenandoah introduce ritardi di 10ms o meno. Se il pacing fallisce, Shenandoah passerà alla fase successiva: GC degenerato.
  • GC degenerato: Se si verifica un errore di allocazione, Shenandoah inizia una fase di stop-the-world. Utilizza la fase per completare il ciclo GC corrente. Poiché uno stop-the-world non contende le risorse all’applicazione, il ciclo dovrebbe finire rapidamente e cancellare il deficit di allocazione. Spesso, un ciclo degenerato avviene dopo che la maggior parte del lavoro del ciclo è già stato completato, quindi lo stop-the-world è breve. Il log del GC lo riporterà come una pausa completa, comunque.
  • GC completo: Se sia il pacing che un GC degenerato falliscono, Shenandoah ricade in un ciclo GC completo. Questo GC finale garantisce che l’applicazione non fallisca con un errore di out-of-memory a meno che non sia rimasto alcuno heap.

Shenandoah offre gli stessi vantaggi di ZGC con grandi heap ma più opzioni di regolazione. A seconda della natura della vostra applicazione, le diverse euristiche possono essere una buona soluzione. I suoi tempi di pausa potrebbero non essere brevi come quelli di ZGC, ma sono più prevedibili.

Per abilitare il Garbage Collector Shenandoah, possiamo usare il seguente argomento:

java -XX:+UseShenanodoahC -jar Application.java

Conclusione

L’intero scopo di questo articolo è di riassumere tutti i garbage collector. Quindi alcune parti del contenuto sono state estratte dai riferimenti dati. Dobbiamo avere un’idea chiara sui garbage collector per selezionare un garbage collector ottimale per i casi d’uso della nostra applicazione. Il garbage collector ottimale migliorerà significativamente le prestazioni della nostra applicazione.

Riferimento

  1. https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
  2. https://www.baeldung.com/jvm-garbage-collectors
  3. https://dzone.com/articles/java-garbage-collection-3
  4. https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/
  5. https://openjdk.java.net/projects/shenandoah/
  6. https://docs.oracle.com/en/java/javase/12/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0
  7. http://clojure-goes-fast.com/blog/shenandoah-in-production/

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.