Containerizzare le applicazioni Java

Questo articolo offre una panoramica delle strategie e delle impostazioni consigliate per l'inserimento in contenitori di applicazioni Java.

Quando si esegue il contenitore di un'applicazione Java, valutare attentamente il tempo di CPU disponibile per il contenitore. Considerare quindi la quantità di memoria disponibile sia in termini di quantità totale di memoria che delle dimensioni dell'heap della macchina virtuale Java (JVM). Negli ambienti in contenitori, le applicazioni possono avere accesso a tutti i processori e quindi essere in grado di eseguire più thread in parallelo. È tuttavia comune che i contenitori abbiano una quota di CPU applicata che potrebbe limitare l'accesso alle CPU.

La JVM ha euristica per determinare il numero di "processori disponibili" in base alla quota della CPU, che può influenzare notevolmente le prestazioni delle applicazioni Java. La memoria allocata al contenitore stesso e le dimensioni dell'area dell'heap per la JVM sono importanti come i processori. Questi fattori determineranno il comportamento del Garbage Collector (GC) e le prestazioni complessive del sistema.

Containerizzare una nuova applicazione

Quando si inserisce in contenitori un carico di lavoro Java per una nuova applicazione, è necessario tenere conto di due aspetti quando si pensa alla memoria:

  • Memoria allocata al contenitore stesso.
  • Quantità di memoria disponibile per il processo Java.

Informazioni sull'ergonomicità predefinita di JVM

Le applicazioni necessitano di un punto di partenza e di impostazioni. La JVM dispone di ergonomiche predefinite con valori predefiniti basati sul numero di processori disponibili e sulla quantità di memoria nel sistema. I valori predefiniti mostrati nelle tabelle seguenti vengono usati quando la JVM viene avviata senza flag di avvio o parametri specifici.

La tabella seguente illustra l'GC predefinito usato per le risorse disponibili:

Risorse disponibili GC predefinito
Numero qualsiasi di processori
Fino a 1791 MB di memoria
SerialGC
2+ processori
1792 MB o più di memoria
G1GC

La tabella seguente mostra la dimensione massima predefinita dell'heap a seconda della quantità di memoria disponibile nell'ambiente in cui è in esecuzione la JVM:

Memoria disponibile Dimensioni massime predefinite dell'heap
Fino a 256 MB 50% della memoria disponibile
Da 256 MB a 512 MB ~127 MB
Più di 512 MB 25% della memoria disponibile

La dimensione dell'heap iniziale predefinita è 1/64 della memoria disponibile.

Questi valori sono validi per OpenJDK 11 e versioni successive e per la maggior parte delle distribuzioni, tra cui Microsoft Build of OpenJDK, Azul Zulu, EclipseTemo, Oracle OpenJDK e altri.

Determinare la memoria del contenitore

Selezionare una quantità di memoria del contenitore che servirà al meglio il carico di lavoro, a seconda delle esigenze dell'applicazione e dei relativi modelli di utilizzo distintivi. Ad esempio, se l'applicazione crea grafici a oggetti di grandi dimensioni, è probabile che sia necessaria una quantità di memoria maggiore di quella necessaria per le applicazioni con molti oggetti grafici di piccole dimensioni.

Suggerimento

Se non si conosce la quantità di memoria da allocare, un buon punto di partenza è 4 GB.

Determinare la memoria heap di JVM

Quando si alloca memoria heap di JVM, tenere presente che la JVM necessita di più memoria rispetto a quella usata per l'heap JVM. Quando si imposta la memoria heap di JVM massima, non deve mai essere uguale alla quantità di memoria del contenitore perché ciò causerà errori di memoria insufficiente del contenitore e arresti anomali del contenitore.

Suggerimento

Allocare il 75% della memoria del contenitore per l'heap JVM.

In OpenJDK 11 e versioni successive è possibile impostare le dimensioni dell'heap JVM nei modi seguenti:

Descrizione Flag Esempi
Valore fisso -Xmx -Xmx4g
Valore dinamico -XX:MaxRAMPercentage -XX:MaxRAMPercentage=75

Dimensioni minime/iniziali dell'heap

Se l'ambiente ha la certezza di avere una certa quantità di memoria riservata a un'istanza di JVM, ad esempio in un contenitore, è necessario impostare le dimensioni minime dell'heap, o le dimensioni iniziali dell'heap, sulle stesse dimensioni dell'heap massimo. Questa impostazione indica alla JVM che non deve eseguire l'attività di liberare memoria al sistema operativo.

Per impostare una dimensione minima dell'heap, utilizzare -Xms per gli importi assoluti o -XX:InitialRAMPercentage per gli importi percentuali.

Importante

Il flag -XX:MinRAMPercentage, nonostante ciò che suggerisce il nome, viene usato per impostare la percentuale di RAM massima predefinita per i sistemi con un massimo di 256 MB di RAM disponibile nel sistema.

Chart showing the default heap size on OpenJDK 17.

Determinare quale GC usare

In precedenza, è stata determinata la quantità di memoria heap JVM da cui iniziare. Il passaggio successivo consiste nel scegliere il catalogo globale. La quantità massima di memoria heap JVM che si ha spesso è un fattore nella scelta del catalogo globale. Nella tabella seguente vengono descritte le caratteristiche di ogni GC.

Fattori SerialGC ParallelGC G1GC ZGC ShenandoahGC
Numero di core 1 2 2 2 2
Multithreading No
Dimensioni dell'heap Java <4 GBytes <4 GBytes >4 GBytes >4 GBytes >4 GBytes
Sospendi Sì (<1 ms) Sì (<10 ms)
Costi generali Minimo Minimo Moderato Moderato Moderato
Effetto della latenza finale Elevato Alta Alto Basso Moderato
Versione di JDK Tutte le date Tutte le date JDK 8+ JDK 17+ JDK 11+
Ideale per Heap piccoli a core singolo Heap di piccole dimensioni multi-core o carichi di lavoro batch con qualsiasi dimensione dell'heap Reattivo in heap medio-grandi (interazioni request-response/DB) Reattivo in heap medio-grandi (interazioni request-response/DB) Reattivo in heap medio-grandi (interazioni request-response/DB)

Suggerimento

Per la maggior parte delle applicazioni di microservizi per utilizzo generico, iniziare con Parallel GC.

Determinare il numero di core CPU necessari

Per qualsiasi GC diverso da SerialGC, è consigliabile usare due o più core vCPU o almeno 2000mcpu_limit in Kubernetes. Non è consigliabile selezionare un core vCPU inferiore a 1 in ambienti in contenitori.

Suggerimento

Se non si conosce il numero di core da cui iniziare, una buona scelta è 2 core vCPU.

Selezionare un punto di partenza

È consigliabile iniziare con due repliche o istanze in ambienti di orchestrazione dei contenitori come Kubernetes, OpenShift, App Spring di Azure, App Azure Container e servizio app Azure. La tabella seguente riepiloga i punti di partenza consigliati per la containerizzazione della nuova applicazione Java.

Core vCPU Memoria del contenitore Dimensioni dell'heap JVM GC Repliche
2 4 GB 75% ParallelGC 2

I parametri JVM da usare sono: -XX:+UseParallelGC -XX:MaxRAMPercentage=75

Containerizzare un'applicazione esistente (locale)

Se l'applicazione è già in esecuzione in locale o in una macchina virtuale nel cloud, è consigliabile iniziare con:

  • La stessa quantità di memoria a cui l'applicazione ha attualmente accesso.
  • Lo stesso numero di CPU (core vCPU) attualmente disponibili per l'applicazione.
  • Gli stessi parametri JVM attualmente usati.

Se la combinazione di core vCPU e/o memoria del contenitore non è disponibile, selezionare quella più vicina, arrotondando i core vCPU e la memoria del contenitore.

Passaggi successivi

Dopo aver compreso le raccomandazioni generali per la containerizzazione di applicazioni Java, continuare con l'articolo seguente per stabilire una baseline di containerizzazione: