Modello di memoria Java (JVM) – Gestione della memoria in Java

Comprendere il Modello di Memoria JVM, Gestione della Memoria Java è molto importante se si desidera comprendere il funzionamento della Garbage Collection di Java. Oggi esamineremo la gestione della memoria in Java, le diverse parti della memoria JVM e come monitorare ed eseguire l’ottimizzazione della garbage collection.

Modello di Memoria Java (JVM)

Come si può vedere nell’immagine sopra, la memoria JVM è divisa in parti separate. A livello generale, la memoria Heap della JVM è divisa fisicamente in due parti: Generazione Giovane e Generazione Vecchia.

Gestione della Memoria in Java – Generazione Giovane

La generazione giovane è il luogo in cui vengono creati tutti gli oggetti nuovi. Quando la generazione giovane è piena, viene eseguita la garbage collection. Questa garbage collection è chiamata GC Minore. La Generazione Giovane è divisa in tre parti: Memoria Eden e due spazi Memoria Survivor. Punti Importanti sugli Spazi della Generazione Giovane:

  • La maggior parte degli oggetti appena creati si trova nello spazio di memoria Eden.
  • Quando lo spazio Eden si riempie di oggetti, viene eseguito un GC minore e tutti gli oggetti sopravvissuti vengono spostati in uno degli spazi dei sopravvissuti.
  • Il GC minore controlla anche gli oggetti sopravvissuti e li sposta nell’altro spazio dei sopravvissuti. Quindi, in un dato momento, uno degli spazi dei sopravvissuti è sempre vuoto.
  • Gli oggetti che sopravvivono dopo molti cicli di GC vengono spostati nello spazio di memoria della generazione vecchia. Di solito, ciò avviene impostando una soglia per l’età degli oggetti della generazione giovane prima che diventino idonei a essere promossi alla generazione vecchia.

Gestione della memoria in Java – Generazione vecchia

La memoria della generazione vecchia contiene gli oggetti che sono a lunga vita e che sono sopravvissuti dopo molti giri di GC minore. Di solito, la raccolta di rifiuti viene eseguita nella memoria della generazione vecchia quando è piena. La raccolta di rifiuti della generazione vecchia è chiamata GC maggiore e di solito richiede più tempo.

Evento di Stop the World

Tutte le raccolte di rifiuti sono eventi “Stop the World” perché tutti i thread dell’applicazione vengono fermati fino al completamento dell’operazione. Poiché la generazione Young contiene oggetti a breve durata, la GC minore è molto veloce e l’applicazione non ne risente. Tuttavia, la GC maggiore richiede molto tempo perché controlla tutti gli oggetti vivi. La GC maggiore dovrebbe essere ridotta al minimo perché rende l’applicazione non responsiva per la durata della raccolta dei rifiuti. Quindi, se hai un’applicazione responsiva e ci sono molte raccolte di rifiuti maggiori in corso, noterai errori di timeout. La durata della raccolta dei rifiuti dipende dalla strategia utilizzata per la raccolta dei rifiuti. Ecco perché è necessario monitorare e ottimizzare il raccoglitore di rifiuti per evitare timeout nelle applicazioni altamente responsivi.

Modello di memoria Java – Generazione permanente

La generazione permanente o “Perm Gen” contiene i metadati dell’applicazione necessari al JVM per descrivere le classi e i metodi utilizzati nell’applicazione. Si noti che Perm Gen non fa parte della memoria heap di Java. Perm Gen è popolata dal JVM durante l’esecuzione in base alle classi utilizzate dall’applicazione. Perm Gen contiene anche classi e metodi della libreria Java SE. Gli oggetti Perm Gen vengono raccolti durante una raccolta completa dei rifiuti.

Modello di memoria Java – Area Metodo

L’Area Metodo è una parte dello spazio nella Perm Gen e viene utilizzata per memorizzare la struttura delle classi (costanti runtime e variabili statiche) e il codice per i metodi e i costruttori.

Modello di memoria Java – Pool di Memoria

Le Piscine di Memoria vengono create dai gestori di memoria JVM per creare un pool di oggetti immutabili se l’implementazione lo supporta. Il Pool di Stringhe è un buon esempio di questo tipo di pool di memoria. Il Pool di Memoria può appartenere a Heap o a Perm Gen, a seconda dell’implementazione del gestore di memoria JVM.

Modello di memoria Java – Pool delle Costanti Runtime

Il pool delle costanti runtime è la rappresentazione runtime per classe del pool delle costanti in una classe. Contiene costanti runtime della classe e metodi statici. Il pool delle costanti runtime fa parte dell’area metodo.

Modello di memoria Java – Memoria Stack di Java

La memoria Stack di Java viene utilizzata per l’esecuzione di un thread. Contiene valori specifici del metodo che sono di breve durata e riferimenti ad altri oggetti nell’heap che vengono referenziati dal metodo. Dovresti leggere Differenza tra Memoria Stack e Memoria Heap.

Gestione della memoria in Java – Interruttori Memoria Heap di Java

Java fornisce molti interruttori di memoria che possiamo utilizzare per impostare le dimensioni della memoria e i loro rapporti. Alcuni degli interruttori di memoria comunemente usati sono:

VM Switch VM Switch Description
-Xms For setting the initial heap size when JVM starts
-Xmx For setting the maximum heap size.
-Xmn For setting the size of the Young Generation, rest of the space goes for Old Generation.
-XX:PermGen For setting the initial size of the Permanent Generation memory
-XX:MaxPermGen For setting the maximum size of Perm Gen
-XX:SurvivorRatio For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8.
-XX:NewRatio For providing ratio of old/new generation sizes. The default value is 2.

La maggior parte delle volte, le opzioni sopra sono sufficienti, ma se desideri controllare anche altre opzioni, controlla Pagina Ufficiale delle Opzioni JVM.

Gestione della memoria in Java – Garbage Collection di Java

La Garbage Collection di Java è il processo per identificare e rimuovere gli oggetti non utilizzati dalla memoria e liberare spazio per essere allocato agli oggetti creati nel futuro elaborativo. Una delle migliori caratteristiche del linguaggio di programmazione Java è la raccolta automatica dei rifiuti, a differenza di altri linguaggi di programmazione come il C, dove l’allocazione e la deallocazione della memoria sono un processo manuale. Il Garbage Collector è il programma in esecuzione in background che esamina tutti gli oggetti in memoria e individua gli oggetti che non sono referenziati da nessuna parte del programma. Tutti questi oggetti non referenziati vengono eliminati e lo spazio viene recuperato per l’allocazione ad altri oggetti. Uno dei modi di base per la raccolta dei rifiuti prevede tre fasi:

  1. Identificazione: Questa è la prima fase in cui il garbage collector identifica quali oggetti sono in uso e quali non lo sono.
  2. Eliminazione normale: Il garbage collector rimuove gli oggetti non utilizzati e recupera lo spazio libero da allocare ad altri oggetti.
  3. Eliminazione con compattazione: Per una migliore prestazione, dopo aver eliminato gli oggetti non utilizzati, tutti gli oggetti sopravvissuti possono essere spostati per essere vicini. Questo aumenterà le prestazioni dell’allocazione di memoria per gli oggetti più nuovi.

Ci sono due problemi con un approccio di marcatura e eliminazione semplice.

  1. La prima è che non è efficiente perché la maggior parte degli oggetti appena creati diventeranno inutilizzati
  2. . In secondo luogo, gli oggetti che sono in uso per più cicli di garbage collection sono molto probabilmente in uso anche per i futuri cicli

. Le precedenti carenze dell’approccio semplice sono il motivo per cui la Garbage Collection di Java è Generazionale e abbiamo spazi Generazione Giovane e Generazione Vecchia nella memoria heap. Ho già spiegato sopra come gli oggetti vengono esaminati e spostati da uno spazio generazionale all’altro in base al Minor GC e al Major GC

Gestione della Memoria in Java – Tipi di Garbage Collection in Java

Esistono cinque tipi di garbage collection che possiamo utilizzare nelle nostre applicazioni. Dobbiamo solo utilizzare lo switch JVM per abilitare la strategia di garbage collection per l’applicazione. Esaminiamoli uno per uno

  1. GC Seriale (-XX:+UseSerialGC): Il GC Seriale utilizza l’approccio semplice marcatore-liberatore-compatto per la garbage collection delle generazioni giovane e vecchia, ovvero Minor e Major GC. Il GC Seriale è utile nelle macchine client come le nostre semplici applicazioni standalone e nelle macchine con CPU più piccole. È buono per piccole applicazioni con bassa impronta di memoria.
  2. GC parallelo (-XX:+UseParallelGC): Il GC parallelo è lo stesso del GC seriale tranne che genera N thread per la raccolta dei rifiuti della generazione giovane dove N è il numero di core della CPU nel sistema. Possiamo controllare il numero di thread utilizzando l’opzione JVM -XX:ParallelGCThreads=n. Il Garbage Collector parallelo è anche chiamato collector di throughput perché utilizza più CPU per velocizzare le prestazioni del GC. Il GC parallelo utilizza un singolo thread per la raccolta dei rifiuti della generazione vecchia.
  3. GC Vecchio Parallelo (-XX:+UseParallelOldGC): Questo è lo stesso del GC parallelo tranne che utilizza più thread sia per la generazione giovane che per la generazione vecchia della raccolta dei rifiuti.
  4. Collezionista di Marcia Simultanea (CMS) (-XX:+UseConcMarkSweepGC): Il Collezionista CMS è anche chiamato collezionista a bassa pausa concorrente. Effettua la raccolta dei rifiuti per la generazione vecchia. Il collezionista CMS cerca di ridurre al minimo le pause dovute alla raccolta dei rifiuti facendo la maggior parte del lavoro di raccolta dei rifiuti in modo concorrente con i thread dell’applicazione. Il collezionista CMS sulla generazione giovane utilizza lo stesso algoritmo di quello del collezionista parallelo. Questo raccoglitore di rifiuti è adatto per le applicazioni reattive dove non possiamo permetterci tempi di pausa più lunghi. Possiamo limitare il numero di thread nel collezionista CMS utilizzando l’opzione JVM -XX:ParallelCMSThreads=n.
  5. G1 Garbage Collector (-XX:+UseG1GC): Il Garbage First o raccoglitore di rifiuti G1 è disponibile da Java 7 e il suo obiettivo a lungo termine è quello di sostituire il raccoglitore CMS. Il raccoglitore G1 è un raccoglitore di rifiuti parallelo, concorrente e compattante incrementalmente a bassa pausa. Il Garbage First Collector non funziona come gli altri raccoglitori e non c’è alcun concetto di spazio Young e Old generation. Divide lo spazio heap in più regioni di dimensioni uguali. Quando viene invocata una raccolta dei rifiuti, raccoglie prima la regione con meno dati vivi, quindi “Garbage First”. Puoi trovare ulteriori dettagli su di esso alla Documentazione Oracle sul raccoglitore Garbage-First.

Gestione della memoria in Java – Monitoraggio della raccolta dei rifiuti Java

Possiamo utilizzare la riga di comando Java così come gli strumenti UI per monitorare le attività di garbage collection di un’applicazione. Per il mio esempio, sto utilizzando una delle demo fornite dai download di Java SE. Se vuoi utilizzare la stessa applicazione, vai alla pagina Download di Java SE e scarica JDK 7 e Demo e Campioni di JavaFX. L’applicazione di esempio che sto utilizzando è Java2Demo.jar ed è presente nella directory jdk1.7.0_55/demo/jfc/Java2D. Tuttavia questo è un passaggio opzionale e puoi eseguire i comandi di monitoraggio del GC per qualsiasi applicazione Java. Il comando usato da me per avviare l’applicazione di esempio è:

pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar

jstat

Possiamo utilizzare lo strumento da riga di comando jstat per monitorare la memoria della JVM e le attività di garbage collection. Viene fornito con il JDK standard, quindi non è necessario fare altro per ottenerlo. Per eseguire jstat è necessario conoscere l’ID del processo dell’applicazione, che è possibile ottenere facilmente utilizzando il comando ps -eaf | grep java.

pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
  501 9582  11579   0  9:48PM ttys000    0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
  501 14073 14045   0  9:48PM ttys002    0:00.00 grep Java2Demo.jar

Quindi l’ID del processo per la mia applicazione Java è 9582. Ora possiamo eseguire il comando jstat come mostrato di seguito.

pankaj@Pankaj:~$ jstat -gc 9582 1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
1024.0 1024.0  0.0    0.0    8192.0   7933.3   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8026.5   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8030.0   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8122.2   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8171.2   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  48.7   0.0    8192.0   106.7    42108.0    23401.3   20480.0 19990.9    158    0.275  40      1.381    1.656
1024.0 1024.0  48.7   0.0    8192.0   145.8    42108.0    23401.3   20480.0 19990.9    158    0.275  40      1.381    1.656

L’ultimo argomento per jstat è l’intervallo di tempo tra ogni output, quindi stamperà dati sulla memoria e sulla garbage collection ogni 1 secondo. Passiamo ora attraverso ciascuna delle colonne una per una.

  • S0C e S1C: Questa colonna mostra la dimensione attuale delle aree Survivor0 e Survivor1 in KB.
  • S0U e S1U: Questa colonna mostra l’utilizzo attuale delle aree Survivor0 e Survivor1 in KB. Nota che una delle aree survivor è sempre vuota.
  • EC e EU: Queste colonne mostrano la dimensione attuale e l’uso dello spazio Eden in KB. Nota che la dimensione di EU sta aumentando e non appena supera EC, viene chiamata una GC minore e la dimensione di EU viene ridotta.
  • OC e OU: Queste colonne mostrano la dimensione attuale e l’uso corrente della generazione vecchia in KB.
  • PC e PU: Queste colonne mostrano la dimensione attuale e l’uso corrente della generazione Perm in KB.
  • YGC e YGCT: La colonna YGC mostra il numero di eventi GC che si sono verificati nella generazione giovane. La colonna YGCT mostra il tempo accumulato per le operazioni GC per la generazione giovane. Nota che entrambi aumentano nella stessa riga in cui il valore di EU viene ridotto a causa di una GC minore.
  • FGC e FGCT: La colonna FGC mostra il numero di eventi Full GC che si sono verificati. La colonna FGCT mostra il tempo accumulato per le operazioni Full GC. Nota che il tempo di Full GC è troppo alto rispetto ai tempi di GC della generazione giovane.
  • GCT: Questa colonna mostra il tempo totale accumulato per le operazioni GC. Nota che è la somma dei valori delle colonne YGCT e FGCT.

Il vantaggio di jstat è che può essere eseguito anche su server remoti dove non abbiamo GUI. Nota che la somma di S0C, S1C e EC è 10m come specificato tramite l’opzione JVM -Xmn10m.

Java VisualVM con Visual GC

Se vuoi visualizzare le operazioni di memoria e GC nell’interfaccia grafica, allora puoi utilizzare lo strumento jvisualvm. Java VisualVM fa anche parte del JDK, quindi non è necessario scaricarlo separatamente. Basta eseguire il comando jvisualvm nel terminale per avviare l’applicazione Java VisualVM. Una volta avviata, è necessario installare il plugin Visual GC dalla sezione Strumenti -< Opzioni Plugin, come mostrato nell’immagine sottostante. Dopo aver installato Visual GC, apri semplicemente l’applicazione dalla colonna di sinistra e vai alla sezione Visual GC. Otterrai un’immagine della memoria JVM e dei dettagli della raccolta dei rifiuti come mostrato nell’immagine sottostante.

Java Garbage Collection Tuning

L’ottimizzazione della Garbage Collection di Java dovrebbe essere l’ultima opzione che dovresti utilizzare per aumentare il throughput della tua applicazione e solo quando noti un calo delle prestazioni a causa di tempi di GC più lunghi che causano timeout dell’applicazione. Se vedi errori java.lang.OutOfMemoryError: spazio PermGen nei log, prova a monitorare e aumentare lo spazio di memoria Perm Gen utilizzando le opzioni JVM -XX:PermGen e -XX:MaxPermGen. Potresti anche provare ad utilizzare -XX:+CMSClassUnloadingEnabled e controllare come si comporta con il raccoglitore di rifiuti CMS. Se vedi molte operazioni di Full GC, allora dovresti provare ad aumentare lo spazio di memoria della generazione vecchia. Complessivamente, l’ottimizzazione della garbage collection richiede molto impegno e tempo e non esiste una regola rigida e veloce per questo. Dovresti provare diverse opzioni e confrontarle per trovare quella più adatta per la tua applicazione. Questo è tutto per il Modello di Memoria di Java, la Gestione della Memoria in Java e la Garbage Collection, spero che ti aiuti a capire il processo di memoria JVM e di garbage collection.

Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java