Java Virtual Machine - Ottimizzazione del GC

Nell'ultimo capitolo, abbiamo imparato a conoscere vari CC generazionali. In questo capitolo, discuteremo su come regolare il GC.

Dimensione heap

La dimensione dell'heap è un fattore importante per le prestazioni delle nostre applicazioni Java. Se è troppo piccolo, verrà riempito frequentemente e, di conseguenza, dovrà essere raccolto frequentemente dal GC. D'altra parte, se aumentassimo solo la dimensione dell'heap, sebbene debba essere raccolto meno frequentemente, la lunghezza delle pause aumenterebbe.

Inoltre, l'aumento della dimensione dell'heap comporta una grave penalità per il sistema operativo sottostante. Utilizzando il paging, il sistema operativo fa sì che i nostri programmi applicativi vedano molta più memoria di quella effettivamente disponibile. Il sistema operativo gestisce ciò utilizzando uno spazio di swap sul disco, copiando parti inattive dei programmi in esso. Quando queste porzioni sono necessarie, il sistema operativo le copia dal disco alla memoria.

Supponiamo che una macchina abbia 8G di memoria e che la JVM veda 16G di memoria virtuale, la JVM non saprebbe che in realtà sono disponibili solo 8G sul sistema. Richiederà solo 16G dal sistema operativo e, una volta ottenuta quella memoria, continuerà a utilizzarlo. Il sistema operativo dovrà scambiare molti dati dentro e fuori, e questa è un'enorme penalità per le prestazioni del sistema.

E poi arrivano le pause che si verificherebbero durante l'intero GC di tale memoria virtuale. Poiché il GC agirà sull'intero heap per la raccolta e la compattazione, dovrà attendere molto prima che la memoria virtuale venga sostituita dal disco. In caso di un collector simultaneo, i thread in background dovranno attendere molto prima che i dati vengano copiati dallo spazio di swap alla memoria.

Quindi ecco la domanda su come dovremmo decidere la dimensione ottimale dell'heap. La prima regola è non richiedere mai al sistema operativo più memoria di quella effettivamente presente. Ciò eviterebbe totalmente il problema di frequenti scambi. Se la macchina ha più JVM installate e in esecuzione, la richiesta di memoria totale da parte di tutte combinate è inferiore alla RAM effettiva presente nel sistema.

È possibile controllare la dimensione della richiesta di memoria da parte della JVM utilizzando due flag:

  • -XmsN - Controlla la memoria iniziale richiesta.

  • -XmxN - Controlla la memoria massima che può essere richiesta.

I valori predefiniti di entrambi questi flag dipendono dal sistema operativo sottostante. Ad esempio, per JVM 64b in esecuzione su MacOS, -XmsN = 64M e -XmxN = minimo 1G o 1/4 della memoria fisica totale.

Notare che la JVM può regolare automaticamente tra i due valori. Ad esempio, se rileva che è in corso una quantità eccessiva di GC, continuerà ad aumentare la dimensione della memoria fintanto che è inferiore a -XmxN e gli obiettivi di prestazioni desiderati vengono raggiunti.

Se sai esattamente quanta memoria ha bisogno la tua applicazione, puoi impostare -XmsN = -XmxN. In questo caso, la JVM non ha bisogno di calcolare un valore "ottimale" dell'heap e, di conseguenza, il processo GC diventa un po 'più efficiente.

Dimensioni della generazione

Puoi decidere quanta parte dell'heap vuoi allocare a YG e quanta parte vuoi allocare all'OG. Entrambi questi valori influenzano le prestazioni delle nostre applicazioni nel modo seguente.

Se la dimensione dell'YG è molto grande, verrà raccolta meno frequentemente. Ciò comporterebbe un numero inferiore di oggetti promossi al OG. D'altra parte, se aumenti troppo le dimensioni di OG, raccoglierlo e compattarlo richiederebbe troppo tempo e questo porterebbe a lunghe pause STW. Pertanto, l'utente deve trovare un equilibrio tra questi due valori.

Di seguito sono riportati i flag che puoi utilizzare per impostare questi valori:

  • -XX:NewRatio=N: Rapporto tra YG e OG (valore predefinito = 2)

  • -XX:NewSize=N: La dimensione iniziale di YG

  • -XX:MaxNewSize=N: Dimensione massima di YG

  • -XmnN: Impostare NewSize e MaxNewSize sullo stesso valore utilizzando questo flag

La dimensione iniziale dell'YG è determinata dal valore di NewRatio dalla formula data -

(total heap size) / (newRatio + 1)

Poiché il valore iniziale di newRatio è 2, la formula precedente fornisce il valore iniziale di YG pari a 1/3 della dimensione totale dell'heap. È sempre possibile sovrascrivere questo valore specificando esplicitamente la dimensione dell'YG utilizzando il flag NewSize. Questo flag non ha alcun valore predefinito e, se non è impostato esplicitamente, la dimensione dell'YG continuerà a essere calcolata utilizzando la formula sopra.

Permagen e Metaspace

Il permagen e il metaspace sono aree di heap in cui la JVM conserva i metadati delle classi. Lo spazio è chiamato "permagen" in Java 7, e in Java 8 è chiamato "metaspace". Queste informazioni vengono utilizzate dal compilatore e dal runtime.

Puoi controllare la dimensione del permagen utilizzando i seguenti flag: -XX: PermSize=N e -XX:MaxPermSize=N. Le dimensioni di Metaspace possono essere controllate utilizzando:-XX:Metaspace- Size=N e -XX:MaxMetaspaceSize=N.

Ci sono alcune differenze nel modo in cui vengono gestiti permagen e metaspace quando i valori del flag non sono impostati. Per impostazione predefinita, entrambi hanno una dimensione iniziale predefinita. Ma mentre il metaspace può occupare tutto l'heap necessario, permagen non può occupare più dei valori iniziali predefiniti. Ad esempio, la JVM 64b ha 82M di spazio heap come dimensione massima permagen.

Si noti che poiché il metaspace può occupare quantità illimitate di memoria a meno che non venga specificato di non farlo, potrebbe esserci un errore di memoria insufficiente. Ogni volta che queste regioni vengono ridimensionate, viene eseguito un GC completo. Quindi, durante l'avvio, se ci sono molte classi che vengono caricate, il metaspace può continuare a ridimensionarsi ottenendo ogni volta un GC completo. Pertanto, l'avvio di applicazioni di grandi dimensioni richiede molto tempo nel caso in cui la dimensione iniziale del metaspace sia troppo bassa. È una buona idea aumentare la dimensione iniziale in quanto riduce il tempo di avvio.

Sebbene il permagen e il metaspace contengano i metadati della classe, non sono permanenti e lo spazio viene recuperato dal GC, come nel caso degli oggetti. Questo è in genere nel caso delle applicazioni server. Ogni volta che si effettua una nuova distribuzione sul server, i vecchi metadati devono essere ripuliti poiché i nuovi caricatori di classi ora avranno bisogno di spazio. Questo spazio viene liberato dal GC.