Motivi per passare a Java 11 e versioni successive

La domanda non è se è necessario passare a Java 11 o versione successiva, ma quando. Nei prossimi anni, Java 8 non sarà più supportato e gli utenti dovranno passare a Java 11 o versioni successive. Il passaggio a Java 11 offre diversi vantaggi ed è auspicabile completarlo il prima possibile.

Rispetto a Java 8 sono state aggiunte nuove funzionalità e sono state effettuate ottimizzazioni. Sono state apportate aggiunte e modifiche di rilievo all'API e sono state introdotte ottimizzazioni per migliorare l'avvio, le prestazioni e l'utilizzo della memoria.

Transizione a Java 11

La transizione a Java 11 può essere eseguita in maniera graduale. Per l'esecuzione in Java 11, non è necessario che il codice usi i moduli Java. È possibile usare Java 11 per eseguire codice sviluppato e compilato con JDK 8. Esistono tuttavia alcuni potenziali problemi, soprattutto per quanto riguarda l'API deprecata, i caricatori di classe e la reflection.

Microsoft Java Engineering Group offre una guida alla transizione da Java 8 a Java 11. La piattaforma Java, edizione Standard Guida alla migrazione di Oracle JDK 9 e Lo stato del sistema di moduli: compatibilità e migrazione sono altre guide utili.

Modifiche di rilievo tra Java 8 e 11

Questa sezione non enumera tutte le modifiche apportate nelle versioni Java 9 [1], 10 [2]e 11 [3]. Vengono evidenziate solo le modifiche che incidono su prestazioni, diagnostica e produttività.

Moduli [4]

I moduli risolvono i problemi di configurazione e incapsulamento difficili da gestire nelle applicazioni su larga scala eseguite in classpath. Un modulo è una raccolta autodescrittiva di classi e interfacce Java e di risorse correlate.

I moduli consentono di personalizzare le configurazioni di runtime che contengono solo i componenti richiesti da un'applicazione. Questa personalizzazione crea un footprint ridotto e consente di collegare un'applicazione in modo statico, tramite jlink, a un runtime personalizzato per la distribuzione. Questo footprint ridotto può essere particolarmente utile in un'architettura di microservizi.

Internamente, la JVM è in grado di sfruttare i vantaggi dei moduli in modo da rendere più efficiente il caricamento delle classi. Il risultato è un runtime più piccolo, più leggero e più veloce da avviare. Le tecniche di ottimizzazione usate dalla JVM per migliorare le prestazioni delle applicazioni possono risultare più efficaci perché i moduli codificano i componenti richiesti da una classe.

Per i programmatori, i moduli consentono di applicare un incapsulamento avanzato richiedendo la dichiarazione esplicita dei pacchetti esportati da un modulo e dei componenti necessari, oltre a limitare l'accesso riflettente. Questo livello di incapsulamento rende un'applicazione più sicura e più semplice da gestire.

L'applicazione può continuare a usare classpath e non è necessario eseguire la transizione ai moduli come requisito per l'esecuzione in Java 11.

Profilatura e diagnostica

Java Flight Recorder [5]

Java Flight Recorder (JFR) raccoglie dati di diagnostica e profilatura da un'applicazione Java in esecuzione. JFR ha un effetto minimo su un'applicazione Java in esecuzione. I dati raccolti possono quindi essere analizzati con Java Mission Control (JMC) e altri strumenti. Mentre in Java 8 JFR e JMC sono funzionalità commerciali, in Java 11 sono open source.

Java Mission Control [6]

Java Mission Control (JMC) fornisce una visualizzazione grafica dei dati raccolti da Java Flight Recorder (JFR) ed è open source in Java 11. Oltre alle informazioni generali sull'applicazione in esecuzione, JMC consente all'utente di eseguire il drill-down dei dati. JFR e JMC si possono usare per diagnosticare problemi di runtime, ad esempio perdite di memoria, sovraccarico di GC, metodi caldi, colli di bottiglia dei thread e I/O bloccanti.

Registrazione unificata [7]

Java 11 include un sistema di registrazione comune per tutti i componenti della JVM. Questo sistema di registrazione unificata consente all'utente di definire quali componenti registrare e a quale livello. La registrazione con granularità fine è utile per eseguire l'analisi della causa radice negli arresti anomali della JVM e per diagnosticare i problemi di prestazioni in un ambiente di produzione.

Profilatura dell'heap a basso sovraccarico [8]

In Java Virtual Machine Tool Interface (JVMTI) è stata aggiunta una nuova API per il campionamento delle allocazioni dell'heap Java. Il campionamento presenta un sovraccarico ridotto e può essere abilitato in modo continuo. Mentre l'allocazione dell'heap può essere monitorata con Java Flight Recorder (JFR), il metodo di campionamento in JFR funziona solo con le allocazioni. L'implementazione di JFR potrebbe anche perdere allocazioni. Al contrario, il campionamento dell'heap in Java 11 può fornire informazioni su oggetti attivi e inattivi.

I fornitori di Application Performance Monitoring (APM) stanno iniziando a usare questa nuova piattaforma e il Java Engineering Group ne sta esaminando il potenziale uso con gli strumenti di monitoraggio delle prestazioni di Azure.

StackWalker [9]

Durante la registrazione viene spesso usata l'acquisizione di uno snapshot dello stack per il thread corrente. Il problema riguarda la quantità di dati dell'analisi dello stack da registrare e la decisione se eseguire effettivamente questa registrazione o meno. Ad esempio, è possibile che si voglia visualizzare l'analisi dello stack solo per una determinata eccezione di un metodo. La classe StackWalker (aggiunta in Java 9) fornisce uno snapshot dello stack e prevede metodi che consentono al programmatore di controllare con granularità fine il modo in cui utilizzare l'analisi dello stack.

Garbage Collection [10]

I Garbage Collector seguenti sono disponibili in Java 11: Serial, Parallel, Garbage-First ed Epsilon. L'impostazione predefinita in Java 11 è Garbage-First Garbage Collector(G1GC).

Gli altri tre collector vengono citati solo per completezza. Il Garbage Collector Z (ZGC) è un collector simultaneo a bassa latenza che tenta di mantenere periodi di pausa inferiori a 10 ms. ZGC è disponibile come funzionalità sperimentale in Java 11. Shenandoah è un collector che riduce i tempi di pausa di GC eseguendo più operazioni di Garbage Collection contemporaneamente al programma Java in esecuzione. Shenandoah è una funzionalità sperimentale di Java 12, ma sono presenti backport in Java 11. Concurrent Mark and Sweep (CMS) è disponibile ma è stato deprecato dopo Java 9.

La JVM definisce le impostazioni predefinite di GC per il caso d'uso tipico. Spesso queste impostazioni predefinite e altre impostazioni di GC devono essere regolate per ottimizzare la velocità effettiva o la latenza, in base ai requisiti dell'applicazione. Per regolare correttamente la Garbage Collection, è necessaria una conoscenza approfondita della tecnologia, competenze che vengono offerte dal Microsoft Java Engineering Group.

G1GC

L'impostazione predefinita in Java 11 è G1 Garbage Collector (G1GC). Lo scopo di G1GC è quello di raggiungere un equilibrio tra latenza e velocità effettiva. Il Garbage Collector G1 prova a raggiungere una velocità effettiva elevata rispettando gli obiettivi del tempo di pausa con un'alta probabilità. G1GC è progettato per evitare raccolte complete, ma quando le raccolte simultanee non possono recuperare memoria abbastanza velocemente, si verificherà un GC completo di fallback. La Garbage Collection completa usa lo stesso numero di thread di lavoro paralleli delle raccolte di nuova generazione e miste.

GC parallelo

Il collector parallelo è l'impostazione predefinita in Java 8. Si tratta di un collector per la velocità effettiva che usa più thread per velocizzare l'operazione di Garbage Collection.

Epsilon [11]

Il Garbage Collector epsilon gestisce le allocazioni, ma non recupera alcuna memoria. Quando l'heap viene esaurito, la JVM verrà arrestata. Epsilon è utile per i servizi di breve durata e per le applicazioni note per essere prive di garbage.

Miglioramenti per i contenitori Docker [12]

Prima di Java 10, i vincoli di memoria e CPU impostati in un contenitore non venivano riconosciuti dalla JVM. In Java 8, ad esempio, la JVM predefinita imposterà come predefinite le dimensioni massime dell'heap su ¼ della memoria fisica dell'host sottostante. A partire da Java 10, la JVM usa i vincoli impostati dai gruppi di controllo contenitore (cgroups) per impostare i limiti di memoria e CPU (vedere la nota di seguito). Ad esempio, le dimensioni massime predefinite dell'heap sono ¼ del limite di memoria del contenitore (ad esempio, 500 MB per -m2G).

Sono state inoltre aggiunte opzioni della JVM per offrire agli utenti di contenitori Docker un controllo granulare sulla quantità di memoria di sistema che verrà usata per l'heap Java.

Questo supporto è abilitato per impostazione predefinita ed è disponibile solo nelle piattaforme basate su Linux.

Nota

Per la maggior parte del supporto di cgroup è stato eseguito il backporting a Java 8 a partire da jdk8u191. Ulteriori miglioramenti potrebbero non essere necessariamente sottoposti a backporting alla versione 8.

File JAR a più versioni [13]

In Java 11 è possibile creare un file JAR contenente più versioni dei file di classe specifiche della versione Java. I file JAR con più versioni consentono agli sviluppatori di librerie di supportare più versioni di Java senza dover distribuire più versioni dei file JAR. Per il consumer di queste librerie, i file JAR con più versioni risolvono il problema di dover abbinare file JAR specifici a destinazioni di runtime specifiche.

Miglioramenti vari delle prestazioni

Le modifiche seguenti apportate alla JVM hanno un impatto diretto sulle prestazioni.

  • JEP 197: Segmented Code Cache [14] - Divide la cache del codice in segmenti distinti. Questa segmentazione assicura un maggior controllo del footprint di memoria della JVM, riduce il tempo di analisi dei metodi compilati, riduce significativamente la frammentazione della cache del codice e migliora le prestazioni.

  • JEP 254: stringhe di compattazione [15] - Modifica la rappresentazione interna di una stringa da due byte per char a uno o due byte per char, a seconda della codifica char. Poiché la maggior parte delle stringhe contiene caratteri ISO-8859-1/Latin-1, questa modifica dimezza di fatto la quantità di spazio necessaria per archiviare una stringa.

  • JEP 310: Applicazione Class-Data Condivisione [16] - Class-Data Condivisione riduce il tempo di avvio consentendo alle classi archiviate di essere mappate in fase di esecuzione. Questa funzionalità estende la condivisione di dati delle classi consentendo di inserire le classi dell'applicazione nell'archivio CDS. Quando più JVM condividono lo stesso file di archivio, viene risparmiata memoria i tempi di risposta generali del sistema migliorano.

  • JEP 312: Thread-Local Handshakes [17] - Consente di eseguire un callback nei thread senza eseguire un punto sicuro della macchina virtuale globale, che consente alla macchina virtuale di ottenere una latenza inferiore riducendo il numero di punti di sicurezza globali.

  • Allocazione lazy dei thread del compilatore [18] - In modalità di compilazione a livelli, la macchina virtuale avvia un numero elevato di thread del compilatore. Questa modalità è l'impostazione predefinita nei sistemi con molte CPU. Questi thread vengono creati indipendentemente dalla memoria disponibile o dal numero di richieste di compilazione. I thread consumano memoria anche quando sono inattivi (ovvero quasi sempre), il che comporta un uso inefficiente delle risorse. Per risolvere questo problema, l'implementazione è stata cambiata per cui durante l'avvio del sistema viene avviato solo un thread del compilatore di ogni tipo. L'avvio di thread aggiuntivi e l'arresto di quelli inutilizzati vengono gestiti dinamicamente.

Le modifiche seguenti apportate alle librerie principali hanno un impatto sulle prestazioni del codice nuovo o modificato.

  • JEP 193: Handle variabili [19] - Definisce un metodo standard per richiamare gli equivalenti di vari java.util.concurrent.atomic e sun.misc.Unsafe operazioni su campi oggetti e elementi matrice, un set standard di operazioni di recinzione per il controllo granulare della memoria e un'operazione standard di copertura-recinto per garantire che un oggetto a cui si fa riferimento rimanga fortemente raggiungibile.

  • JEP 269: Metodi pratici per le raccolte [20] - Definisce le API della libreria per creare istanze di raccolte e mappe con un numero ridotto di elementi. Si stratta di metodi factory statici per le interfacce di raccolte che creano istanze di raccolte compatte e non modificabili. Queste istanze sono intrinsecamente più efficienti. Le API creano raccolte rappresentate in maniera compatta e prive di una classe wrapper.

  • JEP 285: Spin-Wait Hint [21] - Fornisce l'API che consente a Java di suggerire al sistema di runtime che si trova in un ciclo di rotazione. Determinate piattaforme hardware traggono vantaggio dal software che indica che un thread è in uno stato di occupato in attesa.

  • JEP 321: Client HTTP (Standard) [22]- Fornisce una nuova API client HTTP che implementa HTTP/2 e WebSocket e può sostituire l'API HttpURLConnection legacy.

Riferimenti

[1] Oracle Corporation, "Note sulla versione di Java Development Kit 9", (Online). Disponibile: https://www.oracle.com/technetwork/java/javase/9u-relnotes-3704429.html. (Ultimo accesso: 13 novembre 2019).

[2] Oracle Corporation, "Java Development Kit 10 Release Notes", (Online). Disponibile: https://www.oracle.com/technetwork/java/javase/10u-relnotes-4108739.html. (Ultimo accesso: 13 novembre 2019).

[3] Oracle Corporation, "Note sulla versione di Java Development Kit 11" (Online). Disponibile: https://www.oracle.com/technetwork/java/javase/11u-relnotes-5093844.html. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation, "Project Jigsaw", 22 settembre 2017. (Online). Disponibile: http://openjdk.java.net/projects/jigsaw/. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 328: Flight Recorder", 9 settembre 2018. (online). Disponibile: http://openjdk.java.net/jeps/328. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "Mission Control", 25 aprile 2019. (online). Disponibile: https://wiki.openjdk.java.net/display/jmc/Main. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 158: Registrazione JVM unificata", 14 febbraio 2019. (online). Disponibile: http://openjdk.java.net/jeps/158. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 331: Low-Overhead Profiling Heap", 5 settembre 2018. (online). Disponibile: http://openjdk.java.net/jeps/331. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 259: API Stack-Walking", 18 luglio 2017. (online). Disponibile: http://openjdk.java.net/jeps/259. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 248: Make G1 the Default Garbage Collector", 12 settembre 2017. (online). Disponibile: http://openjdk.java.net/jeps/248. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 318: Epsilon: A No-Op Garbage Collector", 24 settembre 2018. (online). Disponibile: http://openjdk.java.net/jeps/318. (Ultimo accesso: 13 novembre 2019).

[12] Oracle Corporation, "JDK-8146115: Migliorare il rilevamento dei contenitori docker e l'utilizzo della configurazione delle risorse", 16 settembre 2019. (online). Disponibile: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation, "JEP 238: File JAR multi-rilascio", 22 giugno 2017. (online). Disponibile: http://openjdk.java.net/jeps/238. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 197: Segmented Code Cache", 28 aprile 2017. (online). Disponibile: http://openjdk.java.net/jeps/197. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation, "JEP 254: Compact Strings", 18 maggio 2019. (online). Disponibile: http://openjdk.java.net/jeps/254. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation, "JEP 310: Application Class-Data Sharing", 17 agosto 2018. (online). Disponibile: https://openjdk.java.net/jeps/310. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 312: Thread-Local Handshakes", 21 agosto 2019. (online). Disponibile: https://openjdk.java.net/jeps/312. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JDK-8198756: allocazione lazy dei thread del compilatore", 29 ottobre 2018. (online). Disponibile: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation, "JEP 193: Handle variabili", 17 agosto 2017. (online). Disponibile: https://openjdk.java.net/jeps/193. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 269: Pratici metodi factory per le raccolte", 26 giugno 2017. (online). Disponibile: https://openjdk.java.net/jeps/269. (Ultimo accesso: 13 novembre 2019).

Oracle Corporation , "JEP 285: hint Spin-Wait", 20 agosto 2017. (online). Disponibile: https://openjdk.java.net/jeps/285. (Ultimo accesso: 13 novembre 2019).

[22] Oracle Corporation, "JEP 321: HTTP Client (Standard)," 27 settembre 2018. (online). Disponibile: https://openjdk.java.net/jeps/321. (Ultimo accesso: 13 novembre 2019).