Partager via


Raisons de passer à Java 11 et au-delà

La question n’est pas si vous devez passer à Java 11 ou une version ultérieure, mais quand. Au cours des prochaines années, Java 8 ne sera plus pris en charge et les utilisateurs devront passer à Java 11 ou version ultérieure. Nous argumentons qu’il existe des avantages pour passer à Java 11 et encourager les équipes à le faire dès que possible.

Depuis Java 8, de nouvelles fonctionnalités ont été ajoutées et des améliorations ont été apportées. Il existe des ajouts et des modifications notables dans l’API, et il existe des améliorations qui améliorent l’utilisation du démarrage, des performances et de la mémoire.

Transition vers Java 11

La transition vers Java 11 peut être effectuée de manière pas à pas. Il n’est pas nécessaire que le code utilise des modules Java pour s’exécuter sur Java 11. Java 11 peut être utilisé pour exécuter du code développé et généré avec JDK 8. Toutefois, il existe des problèmes potentiels, principalement concernant l’API déconseillée, les chargeurs de classes et la réflexion.

Le groupe d’ingénierie Microsoft Java a un guide pour passer de Java 8 à Java 11. Le Guide de migration Java Platform, Standard Edition Oracle JDK 9 et l’état du système de module : compatibilité et migration sont d’autres guides utiles.

Modifications de haut niveau entre Java 8 et 11

Cette section n’énumère pas toutes les modifications apportées dans les versions 9 de Java [1], 10 [2] et 11 [3]. Les modifications qui ont un impact sur les performances, les diagnostics et la productivité sont mises en évidence.

Modules [4]

Les modules résolvent les problèmes de configuration et d’encapsulation qui sont difficiles à gérer dans les applications à grande échelle s’exécutant sur le classpath. Un module est une collection auto-décrivant les classes et interfaces Java, ainsi que les ressources associées.

Les modules permettent de personnaliser les configurations d’exécution qui contiennent uniquement les composants requis par une application. Cette personnalisation crée une empreinte plus petite et permet à une application d’être liée statiquement, à l’aide de jlink, dans un runtime personnalisé pour le déploiement. Cette empreinte réduite peut être particulièrement utile dans une architecture de microservices.

En interne, la machine virtuelle JVM peut tirer parti des modules d’une manière qui rend le chargement de classes plus efficace. Le résultat est un runtime plus petit, plus léger et plus rapide à démarrer. Les techniques d’optimisation utilisées par la machine virtuelle JVM pour améliorer les performances des applications peuvent être plus efficaces, car les modules encodent les composants dont une classe a besoin.

Pour les programmeurs, les modules aident à appliquer une encapsulation forte en exigeant une déclaration explicite des paquets qu'un module exporte et des composants qu'il requiert, et en limitant l'accès par réflexion. Ce niveau d’encapsulation rend une application plus sécurisée et plus facile à gérer.

Une application peut continuer à utiliser le classpath et n’a pas besoin de passer aux modules comme condition requise pour l’exécution sur Java 11.

Profilage et diagnostics

Enregistreur de vol Java [5]

Java Flight Recorder (JFR) collecte les données de diagnostic et de profilage à partir d’une application Java en cours d’exécution. JFR a peu d’impact sur une application Java en cours d’exécution. Les données collectées peuvent ensuite être analysées avec Java Mission Control (JMC) et d’autres outils. Alors que JFR et JMC étaient des fonctionnalités commerciales dans Java 8, les deux sont open source dans Java 11.

Java Mission Control [6]

Java Mission Control (JMC) fournit un affichage graphique des données collectées par l’enregistreur de vol Java (JFR) et est open source dans Java 11. En plus des informations générales sur l’application en cours d’exécution, JMC permet à l’utilisateur d’explorer les données. JFR et JMC peuvent être utilisés pour diagnostiquer les problèmes d’exécution tels que les fuites de mémoire, la surcharge GC, les méthodes chaudes, les goulots d’étranglement des threads et le blocage des E/S.

Journalisation unifiée [7]

Java 11 a un système de journalisation commun pour tous les composants de la machine virtuelle JVM. Ce système de journalisation unifié permet à l’utilisateur de définir les composants à consigner et à quel niveau. Cette journalisation affinée est utile pour effectuer une analyse de la cause racine sur les incidents JVM et pour diagnostiquer les problèmes de performances dans un environnement de production.

Profilage de tas à faible surcharge [8]

Une nouvelle API a été ajoutée à l’interface JVMTI (Java Virtual Machine Tool Interface) pour l’échantillonnage des allocations de tas Java. L’échantillonnage a une faible surcharge et peut être activé en continu. Bien que l’allocation de tas puisse être surveillée avec Java Flight Recorder (JFR), la méthode d’échantillonnage dans JFR fonctionne uniquement sur les allocations. L’implémentation JFR peut également rater certaines allocations. En revanche, l’échantillonnage de tas dans Java 11 peut fournir des informations sur les objets vivants et morts.

Les fournisseurs APM (Application Performance Monitoring) commencent à utiliser cette nouvelle fonctionnalité et le groupe d’ingénierie Java examine son utilisation potentielle avec les outils de surveillance des performances Azure.

StackWalker [9]

L’obtention d’un instantané de la pile pour le thread actuel est souvent utilisée pour la journalisation. Le problème est la longueur de la trace de pile à journaliser, et si la trace de pile doit être journalisée tout court. Par exemple, on peut vouloir voir la trace de pile uniquement pour une certaine exception d'une certaine méthode. La classe StackWalker (ajoutée en Java 9) offre une capture de la pile et propose des méthodes qui donnent au programmeur un contrôle précis sur la façon d’utiliser la trace de pile.

Garbage collection [10]

Les collecteurs de mémoire suivants sont disponibles dans Java 11 : Serial, Parallel, Garbage-First et Epsilon. Le ramasse-miettes par défaut dans Java 11 est le Garbage First Garbage Collector (G1GC).

Trois autres collectionneurs sont mentionnés ici pour l’exhaustivité. Le Z Garbage Collector (ZGC) est un collecteur d'ordures simultané à faible latence qui vise à maintenir les temps de pause sous les 10 ms. ZGC est disponible en tant que fonctionnalité expérimentale dans Java 11. Le collecteur Shenandoah est un collecteur à faible pause qui réduit les temps de pause GC en effectuant davantage de ramassage des ordures de manière concurrente avec le programme Java en cours d’exécution. Shenandoah est une fonctionnalité expérimentale dans Java 12, mais il existe des rétroports vers Java 11. Le collecteur de marquage et de balayage simultanés (CMS) est disponible, mais il est déprécié depuis Java 9.

La JVM définit les valeurs par défaut du GC pour le cas d'utilisation typique. Souvent, ces valeurs par défaut et d’autres paramètres GC doivent être paramétrées pour un débit ou une latence optimaux, en fonction des exigences de l’application. Le réglage correct du GC nécessite une connaissance approfondie du GC, expertise que le groupe d’ingénierie Microsoft Java fournit.

G1GC

Le ramasse-miettes par défaut dans Java 11 est le ramasse-miettes G1 (G1GC). L’objectif de G1GC est de trouver un équilibre entre la latence et le débit. Le collecteur de déchets G1 tente d’atteindre un débit élevé en respectant les objectifs de temps de pause avec une grande probabilité. G1GC est conçu pour éviter les GC complets, mais lorsque les collectes simultanées ne peuvent pas récupérer la mémoire suffisamment rapidement, un GC complet de secours se déclenche. Le GC complet utilise le même nombre de threads de travail parallèles que les collections jeunes et mixtes.

GC parallèle

Le collecteur parallèle est le collecteur par défaut dans Java 8. Parallel GC est un collecteur à haut débit qui utilise plusieurs threads pour accélérer la collecte des déchets.

Epsilon [11]

Le collecteur de déchets Epsilon gère les allocations de mémoire, mais ne récupère aucune mémoire. Lorsque le tas est épuisé, la machine virtuelle JVM s’arrête. Epsilon est utile pour les services de courte durée et pour les applications connues pour être sans collecte de débris.

Améliorations apportées aux conteneurs Docker [12]

Avant Java 10, les contraintes de mémoire et d’UC définies sur un conteneur n’ont pas été reconnues par la machine virtuelle JVM. Dans Java 8, par exemple, la JVM définira par défaut la taille maximale du tas à un quart de la mémoire physique de l’hôte sous-jacent. À compter de Java 10, la machine virtuelle JVM utilise des contraintes définies par des groupes de contrôle de conteneur (cgroups) pour définir des limites de mémoire et d’UC (voir la remarque ci-dessous). Par exemple, la taille maximale par défaut du tas est de 1/4 de la limite de mémoire du conteneur (par exemple, 500 Mo pour -m2G).

Les options JVM ont également été ajoutées pour donner aux utilisateurs de conteneurs Docker un contrôle granulaire sur la quantité de mémoire système qui sera utilisée pour le tas Java.

Cette prise en charge est activée par défaut et est disponible uniquement sur les plateformes Linux.

Note

La plupart du travail d'activation de cgroup a été rétroporté dans Java 8 depuis jdk8u191. D’autres améliorations peuvent ne pas nécessairement être rétroportées à 8.

Fichiers jar multiversion [13]

Il est possible dans Java 11 de créer un fichier jar qui contient plusieurs versions spécifiques à java des fichiers de classe. Les fichiers jar multiversion permettent aux développeurs de bibliothèques de prendre en charge plusieurs versions de Java sans avoir à expédier plusieurs versions de fichiers jar. Pour le consommateur de ces bibliothèques, les fichiers jar à versions multiples résolvent le problème de devoir faire correspondre des fichiers jar spécifiques à des cibles runtime spécifiques.

Améliorations diverses des performances

Les modifications suivantes apportées à la machine virtuelle JVM ont un impact direct sur les performances.

  • JEP 197 : Cache de code segmenté [14] : divise le cache de code en segments distincts. Cette segmentation offre un meilleur contrôle de l’empreinte mémoire JVM, réduit le temps d’analyse des méthodes compilées, réduit considérablement la fragmentation du cache de code et améliore les performances.

  • JEP 254 : Chaînes compactes [15] : modifie la représentation interne d’une chaîne de deux octets par char à un ou deux octets par char, en fonction de l’encodage de caractères. Étant donné que la plupart des chaînes contiennent des caractères ISO-8859-1/Latin-1, cette modification réduit efficacement la quantité d’espace nécessaire pour stocker une chaîne.

  • JEP 310: Class-Data Sharing [16] : Le partage de Class-Data diminue le temps de démarrage en permettant aux classes archivées d’être mappées en mémoire au moment de l’exécution. Le partage des données de classe applicative étend le partage des données de classe en permettant aux classes applicatives d’être placées dans l’archive CDS. Lorsque plusieurs machines virtuelles JV partagent le même fichier d’archivage, la mémoire est enregistrée et le temps de réponse système global s’améliore.

  • JEP 312 : Poignées de main Thread-Local [17] - permet d'exécuter un rappel sur des threads sans effectuer de safepoint de machine virtuelle globale, ce qui permet à la machine virtuelle (VM) d'atteindre une latence inférieure en réduisant le nombre de safepoints globaux.

  • Allocation différée des threads du compilateur [18] : en mode de compilation hiérarchisé, la machine virtuelle démarre un grand nombre de threads du compilateur. Ce mode est le mode par défaut sur les systèmes avec de nombreux processeurs. Ces threads sont créés indépendamment de la mémoire disponible ou du nombre de demandes de compilation. Les threads consomment de la mémoire même lorsqu’ils sont inactifs (ce qui est presque tout le temps), ce qui entraîne une utilisation inefficace des ressources. Pour résoudre ce problème, l’implémentation a été modifiée pour démarrer un seul thread de compilateur de chaque type au démarrage. Le démarrage de threads supplémentaires et l’arrêt des threads inutilisés sont gérés dynamiquement.

Les modifications suivantes apportées aux bibliothèques principales ont un impact sur les performances du code nouveau ou modifié.

  • JEP 193 : Poignées de variables [19] : définit un moyen standard d’appeler les équivalents de diverses opérations java.util.concurrent.atomic et sun.misc.Unsafe sur les champs d’objet et les éléments de tableau, un ensemble standard d’opérations de clôture pour le contrôle précis de l’ordre de la mémoire et une opération d’accessibilité standard pour garantir qu’un objet référencé reste fortement accessible.

  • JEP 269 : Méthodes de fabrique pratique pour les collections [20] : définit les API de bibliothèque pour faciliter la création d’instances de collections et de mappages avec un petit nombre d’éléments. Méthodes de fabrique statiques sur les interfaces de collection qui créent des instances de collection compactes et non modifiables. Ces instances sont intrinsèquement plus efficaces. Les API créent des collections qui sont représentées de manière compacte et n’ont pas de classe wrapper.

  • JEP 285 : Indices de Spin-Wait [21] : fournit une API qui permet à Java d’indiquer au système d’exécution qu’il se trouve dans une boucle active. Certaines plateformes matérielles bénéficient d’une indication logicielle indiquant qu’un thread est dans un état d’attente occupé.

  • JEP 321 : Client HTTP (Standard) [22]- Fournit une nouvelle API cliente HTTP qui implémente HTTP/2 et WebSocket et peut remplacer l’API HttpURLConnection héritée.

References

[1] Oracle Corporation, « Notes de publication du Kit de développement Java 9 », (en ligne). Disponible : https://www.oracle.com/technetwork/java/javase/9u-relnotes-3704429.html. (Accessible le 13 novembre 2019).

[2] Oracle Corporation, « Notes de publication du Kit de développement Java 10 », (en ligne). Disponible : https://www.oracle.com/technetwork/java/javase/10u-relnotes-4108739.html. (Accessible le 13 novembre 2019).

[3] Oracle Corporation, « Notes de publication du Kit de développement Java 11 », (en ligne). Disponible : https://www.oracle.com/technetwork/java/javase/11u-relnotes-5093844.html. (Accessible le 13 novembre 2019).

[4] Oracle Corporation, « Project Jigsaw », 22 septembre 2017. (En ligne). Disponible : http://openjdk.java.net/projects/jigsaw/. (Accessible le 13 novembre 2019).

[5] Oracle Corporation, « JEP 328 : Flight Recorder », 9 septembre 2018. (En ligne). Disponible : http://openjdk.java.net/jeps/328. (Accessible le 13 novembre 2019).

[6] Oracle Corporation, « Mission Control », 25 avril 2019. (En ligne). Disponible : https://wiki.openjdk.java.net/display/jmc/Main. (Accessible le 13 novembre 2019).

[7] Oracle Corporation, « JEP 158 : Unified JVM Logging », 14 février 2019. (En ligne). Disponible : http://openjdk.java.net/jeps/158. (Accessible le 13 novembre 2019).

[8] Oracle Corporation, « JEP 331 : Profilage de tas à faible surcharge », 5 septembre 2018. (En ligne). Disponible : http://openjdk.java.net/jeps/331. (Accessible le 13 novembre 2019).

[9] Oracle Corporation, « JEP 259 : Stack-Walking API », 18 juillet 2017. (En ligne). Disponible : http://openjdk.java.net/jeps/259. (Accessible le 13 novembre 2019).

[10] Oracle Corporation, « JEP 248 : Faire de G1 le ramasse-miettes par défaut », 12 septembre 2017. (En ligne). Disponible : http://openjdk.java.net/jeps/248. (Accessible le 13 novembre 2019).

[11] Oracle Corporation, « JEP 318 : Epsilon : A No-Op Garbage Collector », 24 septembre 2018. (En ligne). Disponible : http://openjdk.java.net/jeps/318. (Accessible le 13 novembre 2019).

[12] Oracle Corporation, « JDK-8146115 : Améliorer l’utilisation de la détection et de la configuration des ressources des conteneurs Docker », 16 septembre 2019. (En ligne). Disponible : https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115. (Accessible le 13 novembre 2019).

[13] Oracle Corporation, « JEP 238 : fichiers JAR multiversions », 22 juin 2017. (En ligne). Disponible : http://openjdk.java.net/jeps/238. (Accessible le 13 novembre 2019).

[14] Oracle Corporation, « JEP 197 : Cache de code segmenté », 28 avril 2017. (En ligne). Disponible : http://openjdk.java.net/jeps/197. (Accessible le 13 novembre 2019).

[15] Oracle Corporation, « JEP 254 : Chaînes compactes », 18 mai 2019. (En ligne). Disponible : http://openjdk.java.net/jeps/254. (Accessible le 13 novembre 2019).

[16] Oracle Corporation, « JEP 310 : Application Class-Data Sharing », 17 août 2018. (En ligne). Disponible : https://openjdk.java.net/jeps/310. (Accessible le 13 novembre 2019).

[17] Oracle Corporation, « JEP 312 : Thread-Local Handshakes », 21 août 2019. (En ligne). Disponible : https://openjdk.java.net/jeps/312. (Accessible le 13 novembre 2019).

[18] Oracle Corporation, « JDK-8198756 : Allocation différée des threads du compilateur », 29 octobre 2018. (En ligne). Disponible : https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756. (Accessible le 13 novembre 2019).

[19] Oracle Corporation, « JEP 193 : Variables Handles », 17 août 2017. (En ligne). Disponible : https://openjdk.java.net/jeps/193. (Accessible le 13 novembre 2019).

[20] Oracle Corporation, « JEP 269 : Convenience Factory Methods for Collections », 26 juin 2017. (En ligne). Disponible : https://openjdk.java.net/jeps/269. (Accessible le 13 novembre 2019).

[21] Oracle Corporation, « JEP 285 : Spin-Wait Hints », 20 août 2017. (En ligne). Disponible : https://openjdk.java.net/jeps/285. (Accessible le 13 novembre 2019).

[22] Oracle Corporation, « JEP 321 : HTTP Client (Standard) », 27 septembre 2018. (En ligne). Disponible : https://openjdk.java.net/jeps/321. (Accessible le 13 novembre 2019).