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 sommes convaincus que le passage à Java 11 présente des avantages, et c’est pourquoi nous encourageons les équipes à le faire le plus rapidement possible.

Depuis Java 8, de nouvelles fonctionnalités ont été ajoutées et des améliorations ont été apportées. Des ajouts et des modifications perceptibles ont été apportées à l’API. Le démarrage, les performances et l’utilisation de la mémoire ont également été améliorés.

Transition vers Java 11

Vous pouvez effectuer la transition vers Java 11 de manière progressive. Le code ne doit pas nécessairement utiliser des modules Java pour s’exécuter sur Java 11. Vous pouvez utiliser Java 11 pour exécuter du code développé et généré avec JDK 8. Il existe toutefois des problèmes potentiels, qui sont principalement liés à la dépréciation des API, des chargeurs de classes et des méthodes de réflexion.

Le Microsoft Java Engineering Group propose un guide pour la transition de Java 8 vers Java 11. Les guides Java Platform, Standard Edition Oracle JDK 9 Migration Guide et The State of the Module System: Compatibility and Migration sont également très utiles.

Différences importantes entre Java 8 et Java 11

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

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 classpath. Un module est une collection autodescriptive de classes et d’interfaces Java (plus les ressources associées).

Les modules permettent de personnaliser les configurations de runtime qui contiennent uniquement les composants exigés par une application. Cette personnalisation crée une empreinte plus réduite et permet à une application d’être liée de manière statique, en utilisant 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 Java (JVM) peut tirer parti des modules de manière à optimiser le chargement de classes. Vous obtenez un runtime plus petit, plus clair et plus rapide à démarrer. Les techniques d’optimisation utilisées par JVM pour améliorer les performances des applications peuvent être plus efficaces, car les modules encodent les composants dont une classe a besoin.

Les modules aident les programmeurs à appliquer une encapsulation forte. Ils exigent en effet la déclaration explicite des packages exportés par un module et des composants nécessaires, et limitent l’accès réfléchissant. Ce niveau d’encapsulation rend une application plus sécurisée et plus facile à gérer.

Une application peut continuer à utiliser classpath et ne doit pas nécessairement passer à des modules pour s’exécuter sur Java 11.

Profilage et diagnostics

Enregistreur de vol Java [5]

Java Flight Recorder (JFR) collecte les données de diagnostic et de profilage d’une application Java en cours d’exécution. JFR a un impact minime sur une application Java en cours d’exécution. Vous pouvez ensuite analyser les données collectées avec Java Mission Control (JMC) et d’autres outils. JFR et JMC étaient des fonctionnalités commerciales dans Java 8, mais les deux sont en open source dans Java 11.

Contrôle de mission Java [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 en 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 de runtime tels que les fuites de mémoire, la surcharge du GC, les méthodes à chaud, les goulots d’étranglement au niveau de threads et les E/S à l’origine de blocages.

Journalisation unifiée [7]

Java 11 a un système de journalisation commun pour tous les composants de JVM. Ce système de journalisation unifiée permet à l’utilisateur de définir quels composants journaliser et à quel niveau. Cette journalisation détaillée est utile pour analyser la cause racine des plantages de JVM et 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. Alors que l’allocation du tas peut être supervisée avec Java Flight Recorder (JFR), la méthode d’échantillonnage dans JFR fonctionne uniquement sur les allocations. L’implémentation de JFR peut également rater des allocations. En revanche, l’échantillonnage de tas dans Java 11 peut fournir des informations sur les objets actifs et morts.

Les fournisseurs APM (Application Performance Monitoring) commencent à utiliser cette nouvelle fonctionnalité, et le Java Engineering Group étudie son utilisation potentielle avec les outils d’analyse des performances Azure.

StackWalker [9]

Il est courant d’utiliser un instantané de la pile du thread actuel lors de la journalisation. Le problème est de savoir s’il faut journaliser la trace de la pile et dans quelles proportions. Par exemple, vous souhaiterez peut-être voir uniquement la trace de la pile d’une certaine exception à une méthode. La classe StackWalker (ajoutée dans Java 9) fournit un instantané de la pile ainsi que des méthodes qui permettent au programmeur de contrôler avec précision la façon dont la trace de la pile est consommée.

Garbage collection [10]

Les récupérateurs de mémoire suivants sont disponibles dans Java 11 : Serial, Parallel, Garbage-First et Epsilon. Le récupérateur de mémoire par défaut dans Java 11 est G1GC (Garbage First Garbage Collector).

Trois autres récupérateurs sont mentionnés ici pour leur exhaustivité. ZGC (Z Garbage Collector) est un récupérateur simultané à faible latence qui tente de maintenir les temps de pause sous la barre des 10 ms. ZGC est disponible en tant que fonctionnalité expérimentale dans Java 11. Le récupérateur Shenandoah est un récupérateur à faible pause qui réduit les temps de pause de GC en effectuant davantage d’opérations simultanées de garbage collection avec le programme Java en cours d’exécution. Shenandoah est une fonctionnalité expérimentale de Java 12, mais il existe des backports pour Java 11. Le récupérateur CMS (Concurrent Mark and Sweep) est disponible, mais est déprécié depuis Java 9.

JVM définit les valeurs par défaut de GC pour le cas d’usage moyen. Il est souvent nécessaire de régler ces valeurs par défaut, ainsi que d’autres paramètres du GC, pour optimiser le débit ou la latence conformément aux exigences de l’application. Le réglage approprié du GC nécessite une connaissance approfondie de celui-ci, expertise fournie par le Microsoft Java Engineering Group.

G1GC

Le récupérateur de mémoire par défaut dans Java 11 est G1GC (G1 Garbage Collector). Son objectif est de trouver un juste équilibre entre latence et débit. G1GC tente d’atteindre un débit élevé en répondant aux objectifs de temps de pause avec une probabilité élevée. G1GC est conçu pour éviter les regroupements complets, mais lorsque les collections simultanées ne peuvent pas récupérer la mémoire assez rapidement, un GC complet de secours se produit. Le GC complet utilise le même nombre de threads de travail parallèles que les collections jeunes et mixtes.

Parallel GC

Le récupérateur parallèle est le récupérateur par défaut dans Java 8. Parallel GC est un récupérateur de débit qui utilise plusieurs threads pour accélérer le processus de garbage collection.

Epsilon [11]

Le récupérateur de mémoire Epsilon gère les allocations, mais ne récupère pas de mémoire. Quand le tas est épuisé, JVM s’arrête. Epsilon est utile pour les services de courte durée et pour les applications sans déchets.

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

Avant Java 10, les contraintes en matière de mémoire et de processeur définies sur un conteneur n’étaient pas reconnues par JVM. Dans Java 8, par exemple, JVM affecte par défaut à la taille maximale du tas le quart de la mémoire physique de l’hôte sous-jacent. À compter de Java 10, JVM utilise des contraintes définies par les groupes de contrôle de conteneur (cgroups) pour définir les limites de mémoire et de processeur (voir remarque ci-dessous). Par exemple, la taille maximale du tas par défaut est égale au quart de la limite de la mémoire du conteneur (500 Mo pour -m2G).

Des options JVM ont également été ajoutées pour permettre aux utilisateurs de conteneurs Docker de contrôler précisément la quantité de mémoire système utilisée pour le tas Java.

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

Notes

La majeure partie du travail d’activation de cgroup a été rétroportée dans Java 8 (jdk8u191). Les améliorations supplémentaires ne sont pas nécessairement rétroportées dans Java 8.

Fichiers jar multiversion [13]

Dans Java 11, il est possible de créer un fichier jar contenant plusieurs versions de fichiers de classe spécifiques à Java. Les fichiers jar à plusieurs versions permettent aux développeurs de bibliothèques de prendre en charge plusieurs versions de Java sans avoir à expédier plusieurs versions des fichiers jar. Pour les consommateurs de ces bibliothèques, les fichiers jar à plusieurs versions résolvent le problème de la correspondance de fichiers jar spécifiques à des cibles de runtime spécifiques.

Améliorations diverses des performances

Les modifications suivantes apportées à 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 permet de mieux contrôler l’empreinte mémoire de JVM, de raccourcir le temps d’analyse des méthodes compilées, de réduire considérablement la fragmentation du cache de code et d’améliorer 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, selon l’encodage de caractères. Étant donné que la plupart des chaînes contiennent des caractères ISO-8859-1/Latin-1, cette modification divise par deux la quantité d’espace nécessaire au stockage d’une chaîne.

  • JEP 310 : Le partage d’applications Class-Data [16] : Class-Data partage 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 de données de classe d’application autorise le placement des classes d’application dans l’archive CDS, étendant ainsi le partage de données de classe. Le fait de partager un même fichier d’archive entre plusieurs JVM permet d’économiser de la mémoire et d’améliorer le temps de réponse global du système.

  • JEP 312 : Thread-Local liaisons [17] : permet d’exécuter un rappel sur des threads sans effectuer de point sécurisé de machine virtuelle globale, ce qui permet à la machine virtuelle d’obtenir une latence inférieure en réduisant le nombre de points de sécurité globaux.

  • Allocation différée des threads de compilateur [18] : en mode de compilation hiérarchisé, la machine virtuelle démarre un grand nombre de threads de compilateur. Il s’agit du mode par défaut sur les systèmes dotés 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 (presque tout le temps), ce qui débouche sur une utilisation inefficace des ressources. Pour résoudre ce problème, l’implémentation a été modifiée pour ne démarrer qu’un seul thread de compilateur de chaque type durant le démarrage. Le démarrage de threads supplémentaires et l’arrêt des threads inutilisés sont gérés de manière dynamique.

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 divers 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 de clôture standard pour garantir qu’un objet référencé reste fortement accessible.

  • JEP 269 : Méthodes d’usine de commodité 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. Les méthodes de fabrique statiques sur les interfaces de collection créent des instances de collection compactes et non modifiables. Ces instances sont par nature 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 : Spin-Wait indicateurs [21] : fournit une API qui permet à Java d’indiquer au système d’exécution qu’il se trouve dans une boucle de rotation. Certaines plateformes matérielles bénéficient de l’indication logicielle selon laquelle un thread est dans un état d’attente.

  • 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.

Références

[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 (Accès : 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 (Accès : 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 (Accès : 13 novembre 2019).

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

[5] Oracle Corporation, « JEP 328 : Enregistreur de vol », 9 septembre 2018. (en ligne). Disponible : http://openjdk.java.net/jeps/328 (Accès : 13 novembre 2019).

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

[7] Oracle Corporation, « JEP 158 : Journalisation JVM unifiée », 14 février 2019. (en ligne). Disponible : http://openjdk.java.net/jeps/158 (Accès : 13 novembre 2019).

[8] Oracle Corporation, « JEP 331 : Low-Overhead profilage de tas », 5 septembre 2018. (en ligne). Disponible : http://openjdk.java.net/jeps/331 (Accès : 13 novembre 2019).

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

[10] Oracle Corporation, « JEP 248 : Make G1 the Default Garbage Collector », 12 septembre 2017. (en ligne). Disponible : http://openjdk.java.net/jeps/248 (Accès : 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 (Accès : 13 novembre 2019).

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

[13] Oracle Corporation, « JEP 238 : fichiers JAR multiversion », 22 juin 2017. (en ligne). Disponible : http://openjdk.java.net/jeps/238 (Accès : 13 novembre 2019).

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

[15] Oracle Corporation, « JEP 254 : Chaînes compactes », 18 mai 2019. (en ligne). Disponible : http://openjdk.java.net/jeps/254 (Accès : 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 (Accès : 13 novembre 2019).

[17] Oracle Corporation, « JEP 312 : Thread-Local liaisons », 21 août 2019. (en ligne). Disponible : https://openjdk.java.net/jeps/312 (Accès : 13 novembre 2019).

[18] Oracle Corporation, « JDK-8198756 : Allocation lazy des threads du compilateur », 29 octobre 2018. (en ligne). Disponible : https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756 (Accès : 13 novembre 2019).

[19] Oracle Corporation, « JEP 193 : Handles variables », 17 août 2017. (en ligne). Disponible : https://openjdk.java.net/jeps/193 (Accès : 13 novembre 2019).

[20] Oracle Corporation, « JEP 269 : Méthodes d’usine de commodité pour les collections », 26 juin 2017. (en ligne). Disponible : https://openjdk.java.net/jeps/269 (Accès : 13 novembre 2019).

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

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