Partager via


Transition de Java 8 à Java 11

Il n’existe aucune solution unique pour passer du code Java 8 à Java 11. Pour une application non triviale, le passage de Java 8 à Java 11 peut être une quantité importante de travail. Les problèmes potentiels incluent les API supprimées, les packages déconseillés, l’utilisation de l’API interne, les modifications apportées aux chargeurs de classes et les modifications apportées au garbage collection.

En général, les approches sont de tenter d’exécuter sur Java 11 sans recompiler, ou de compiler avec JDK 11 en premier. Si l’objectif est d’obtenir une application en cours d’exécution aussi rapidement que possible, essayez simplement d’exécuter sur Java 11 est souvent la meilleure approche. Pour une bibliothèque, l’objectif est de publier un artefact compilé et testé avec JDK 11.

Le passage à Java 11 vaut la peine. De nouvelles fonctionnalités ont été ajoutées et des améliorations ont été apportées depuis Java 8. Ces fonctionnalités et améliorations améliorent le démarrage, les performances, l’utilisation de la mémoire et offrent une meilleure intégration aux conteneurs. Et il existe des ajouts et des modifications apportées à l’API qui améliorent la productivité des développeurs.

Ce document traite des outils permettant d’inspecter le code. Il traite également des problèmes que vous pouvez rencontrer et des recommandations pour les résoudre. Vous devez également consulter d’autres guides, tels que le Guide de migration JDK Oracle. Comment rendre le code modulaire existant n’est pas couvert ici.

Boîte à outils

Java 11 a deux outils, jdeprscan et jdeps, qui sont utiles pour extraire les problèmes potentiels. Ces outils peuvent être exécutés sur des fichiers jar ou de classe existants. Vous pouvez évaluer l’effort de transition sans avoir à recompiler.

jdeprscan recherche l’utilisation de l’API déconseillée ou supprimée. L’utilisation de l’API déconseillée n’est pas un problème de blocage, mais il s’agit d’un élément à examiner. Existe-t-il un fichier jar mis à jour ? Devez-vous consigner un problème pour résoudre l’utilisation de l’API déconseillée ? L’utilisation de l’API supprimée est un problème bloquant qui doit être résolu avant d’essayer d’exécuter sur Java 11.

jdeps, qui est un analyseur de dépendances de classe Java. Lorsqu’elle est utilisée avec l’option --jdk-internals , jdeps vous indique quelle classe dépend de l’API interne. Vous pouvez continuer à utiliser l’API interne dans Java 11, mais le remplacement de l’utilisation doit être une priorité. L’outil d’analyse des dépendances Java de la page wiki OpenJDK a recommandé de remplacer certaines API internes JDK couramment utilisées.

Il existe des plug-ins jdeps et jdeprscan pour Gradle et Maven. Nous vous recommandons d’ajouter ces outils à vos scripts de génération.

Le compilateur Java lui-même, javac, est un autre outil de votre boîte à outils. Les avertissements et erreurs que vous obtenez à partir de jdeprscan et jdeps sortent du compilateur. L’avantage d’utiliser jdeprscan et jdeps est que vous pouvez exécuter ces outils sur des fichiers jar et des fichiers de classe existants, y compris des bibliothèques tierces.

Ce que jdeprscan et jdeps ne peuvent pas faire, c'est avertir de l'utilisation de la réflexion pour accéder à l’API encapsulée. L’accès réfléchissant est vérifié au moment de l’exécution. En fin de compte, vous devez exécuter le code sur Java 11 pour savoir avec certitude.

Utilisation de jdeprscan

Le moyen le plus simple d’utiliser jdeprscan consiste à lui donner un fichier jar à partir d’une build existante. Vous pouvez également lui donner un répertoire, tel que le répertoire de sortie du compilateur ou un nom de classe individuel. Utilisez l’option --release 11 pour obtenir la liste la plus complète de l’API déconseillée. Si vous souhaitez prioriser l'API déconseillée à traiter, réglez le paramètre sur --release 8. L’API déconseillée dans Java 8 est susceptible d’être supprimée plus tôt que l’API qui a été déconseillée plus récemment.

jdeprscan --release 11 my-application.jar

L’outil jdeprscan génère un message d’erreur s’il a des difficultés à résoudre une classe dépendante. Par exemple : error: cannot find class org/apache/logging/log4j/Logger. L'ajout de classes dépendantes au --class-path ou l'utilisation du class-path de l'application est recommandée, mais l'outil poursuivra l'analyse sans cela. L’argument est --class-path. Aucune autre variante de l’argument class-path ne fonctionnera.

jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar
error: cannot find class sun/misc/BASE64Encoder
class com/company/Util uses deprecated method java/lang/Double::<init>(D)V

Cette sortie nous indique que la com.company.Util classe appelle un constructeur déconseillé de la java.lang.Double classe. Javadoc recommande l’API à utiliser à la place de l’API déconseillée. Aucune quantité de travail ne résout le error: cannot find class sun/misc/BASE64Encoder problème, car il s’agit de l’API qui a été supprimée. Depuis Java 8, java.util.Base64 doit être utilisé.

Exécutez jdeprscan --release 11 --list pour avoir une idée de l’API qui a été déconseillée depuis Java 8. Pour obtenir la liste des API qui ont été supprimées, exécutez jdeprscan --release 11 --list --for-removal.

Utilisation de jdeps

Utilisez jdeps, avec l’option permettant de rechercher des dépendances sur l’API --jdk-internals interne JDK. L’option --multi-release 11 de ligne de commande est nécessaire pour cet exemple, car log4j-core-2.13.0.jar est un fichier jar multiversion. Sans cette option, jdeps se plaindra s’il trouve un fichier jar multiversion. L’option spécifie la version des fichiers de classe à inspecter.

jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar
Util.class -> JDK removed internal API
Util.class -> jdk.base
Util.class -> jdk.unsupported
   com.company.Util        -> sun.misc.BASE64Encoder        JDK internal API (JDK removed internal API)
   com.company.Util        -> sun.misc.Unsafe               JDK internal API (jdk.unsupported)
   com.company.Util        -> sun.nio.ch.Util               JDK internal API (java.base)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.misc.Unsafe                          See http://openjdk.java.net/jeps/260   

La sortie donne quelques conseils sur l’élimination de l’utilisation de l’API interne JDK ! Si possible, l’API de remplacement est suggérée. Le nom du module dans lequel le package est encapsulé est donné entre parenthèses. Le nom du module peut être utilisé avec --add-exports ou --add-opens s’il est nécessaire d’interrompre explicitement l’encapsulation.

L’utilisation de sun.misc.BASE64Encoder ou sun.misc.BASE64Decoder entraîne un java.lang.NoClassDefFoundError dans Java 11. Le code qui utilise ces API doit être modifié pour utiliser java.util.Base64.

Essayez d’éliminer l’utilisation d’une API provenant du module jdk.unsupported. L’API de ce module fait référence à JDK Enhancement Proposition (JEP) 260 comme remplacement suggéré. En un mot, JEP 260 indique que l’utilisation de l’API interne sera prise en charge jusqu’à ce que l’API de remplacement soit disponible. Même si votre code peut utiliser l’API interne JDK, il continuera à s’exécuter pendant un certain temps. Jetez un coup d’œil à JEP 260, car il pointe vers des remplacements pour une API interne. Les handles de variables peuvent être utilisés à la place d’une API sun.misc.Unsafe , par exemple.

jdeps peut faire plus que simplement analyser l'utilisation des éléments internes du JDK. Il s’agit d’un outil utile pour analyser les dépendances et pour générer des fichiers d’informations de module. Pour plus d’informations, consultez la documentation .

Utilisation de javac

La compilation avec JDK 11 nécessite des mises à jour pour générer des scripts, des outils, des infrastructures de test et des bibliothèques incluses. Utilisez l’option -Xlint:uncheckedjavac pour obtenir les détails sur l’utilisation de l’API interne JDK et d’autres avertissements. Il peut également être nécessaire d’utiliser --add-opens ou --add-reads d’exposer des packages encapsulés au compilateur (voir JEP 261).

Les bibliothèques peuvent envisager l’empaquetage en tant que fichier JAR multiversion. Les fichiers jar multiversion vous permettent de prendre en charge les runtimes Java 8 et Java 11 à partir du même fichier jar. Ils ajoutent de la complexité à la build. La création de fichiers jar multiversions dépasse la portée de ce document.

Fonctionne avec Java 11

La plupart des applications doivent s’exécuter sur Java 11 sans modification. La première chose à essayer est d’exécuter sur Java 11 sans recompiler le code. Cette simple exécution vous permettra d’observer les avertissements et erreurs pouvant survenir. Cette approche obtient un résultat.
application à exécuter sur Java 11 plus rapidement en mettant l’accent sur le minimum nécessaire.

La plupart des problèmes que vous pouvez rencontrer peuvent être résolus sans avoir à recompiler le code. Si un problème doit être résolu dans le code, effectuez le correctif, mais continuez à compiler avec JDK 8. Si possible, travaillez sur l’exécution de l’application avecjava la version 11 avant de compiler avec JDK 11.

Vérifier les options de ligne de commande

Avant d’exécuter sur Java 11, effectuez une analyse rapide des options de ligne de commande. Les options qui ont été supprimées entraînent la sortie de la machine virtuelle Java (JVM). Cette vérification est particulièrement importante si vous utilisez des options de journalisation GC, car elles ont changé considérablement à partir de Java 8. L’outil JaCoLine est un bon outil à utiliser pour détecter les problèmes liés aux options de ligne de commande.

Vérifier les bibliothèques tierces

Une source potentielle de problèmes est des bibliothèques tierces que vous ne contrôlez pas. Vous pouvez mettre à jour de manière proactive des bibliothèques tierces vers des versions plus récentes. Vous pouvez également voir ce qui se passe quand vous exécutez l’application et mettre à jour uniquement les bibliothèques qui le nécessitent. Le problème lié à la mise à jour de toutes les bibliothèques vers une version récente est qu’il est plus difficile de trouver la cause racine en cas d’erreur dans l’application. L’erreur s’est-elle produite en raison d’une bibliothèque mise à jour ? Ou l’erreur a-t-elle été provoquée par une modification dans le runtime ? Le problème lié à la mise à jour uniquement de ce qui est nécessaire est qu’il peut être nécessaire de résoudre plusieurs itérations.

La recommandation ici est d’apporter autant de modifications que possible et de mettre à jour des bibliothèques tierces en tant qu’effort distinct. Si vous mettez à jour une bibliothèque tierce, le plus souvent, vous voudrez la version la plus récente et la meilleure compatible avec Java 11. Selon la distance entre votre version actuelle, vous pouvez adopter une approche plus prudente et effectuer une mise à niveau vers la première version compatible Java 9+.

En plus de consulter les notes de publication, vous pouvez utiliser jdeps et jdeprscan pour évaluer le fichier jar. En outre, le groupe de qualité OpenJDK gère une page wiki de sensibilisation de la qualité qui répertorie l’état de test de nombreux projets FOSS (Free Open Source Software) sur les versions d’OpenJDK.

Définir explicitement la collecte de déchets

Le garbage collector parallèle (Parallel GC) est le GC par défaut dans Java 8. Si l’application utilise la valeur par défaut, le GC doit être défini explicitement avec l’option -XX:+UseParallelGCde ligne de commande. Le récupérateur de mémoire par défaut a été modifié dans Java 9 : il s’agit de G1GC (Garbage First Garbage Collector). Pour effectuer une comparaison équitable d’une application s’exécutant sur Java 8 et Java 11, les paramètres GC doivent être identiques. L’expérience avec les paramètres GC doit être différée jusqu’à ce que l’application ait été validée sur Java 11.

Définir explicitement les options par défaut

Si elle s’exécute sur la machine virtuelle HotSpot, la définition de l’option -XX:+PrintCommandLineFlags de ligne de commande vide les valeurs des options définies par la machine virtuelle, en particulier les valeurs par défaut définies par le GC. Exécutez avec cet indicateur sur Java 8 et utilisez les options imprimées lors de l’exécution sur Java 11. Dans la plupart des cas, les valeurs par défaut sont identiques entre 8 et 11. Mais l’utilisation des paramètres de 8 garantit la parité.

La définition de l’option --illegal-access=warn de ligne de commande est recommandée. Dans Java 11, l’utilisation de la réflexion pour accéder à une API interne du JDK génère un avertissement d’accès réfléchissant illégal. Par défaut, l’avertissement est émis uniquement pour le premier accès illégal. Le paramètre --illegal-access=warn entraîne un avertissement sur chaque accès réfléchissant illégal. Vous trouverez plus de cas d'accès illégal si l'option est définie sur avertir. Mais vous obtiendrez également beaucoup d’avertissements redondants.
Une fois l’application exécutée sur Java 11, définissez --illegal-access=deny pour imiter le comportement futur du runtime Java. À compter de Java 16, la valeur par défaut est --illegal-access=deny.

Avertissements de ClassLoader

Dans Java 8, vous pouvez convertir le chargeur de classe système en un URLClassLoader. Cela est généralement effectué par les applications et les bibliothèques qui souhaitent injecter des classes dans le classpath au moment de l’exécution. La hiérarchie du chargeur de classes a changé dans Java 11. Le chargeur de classe système (également appelé chargeur de classe d’application) est désormais une classe interne. Si vous le castez en URLClassLoader, l’exception ClassCastException est levée au moment de l’exécution. Java 11 n’a pas d’API pour augmenter dynamiquement le classpath au moment de l’exécution, mais il peut être effectué par réflexion, avec les avertissements évidents sur l’utilisation de l’API interne.

Dans Java 11, le chargeur de classe de démarrage charge uniquement les modules principaux. Si vous créez un chargeur de classes avec un parent Null, il se peut qu’il ne trouve pas toutes les classes de plateforme. Dans Java 11, vous devez passer ClassLoader.getPlatformClassLoader() au lieu de null comme chargeur de classes parent dans ce cas.

Modifications des données de localisation

La source par défaut des données de paramètres régionaux dans Java 11 a été modifiée avec JEP 252 en référentiel de données de paramètres régionaux communs du Consortium Unicode. Cela peut avoir un impact sur la mise en forme localisée. Définissez la propriété java.locale.providers=COMPAT,SPI système pour revenir au comportement des paramètres régionaux Java 8, si nécessaire.

Problèmes potentiels

Voici quelques-uns des problèmes courants que vous pouvez rencontrer. Suivez les liens pour plus d’informations sur ces problèmes.

Options non reconnues

Si une option de ligne de commande a été supprimée, l’application affiche Unrecognized option: ou Unrecognized VM option suivi du nom de l’option incriminée. Une option non reconnue entraîne la fermeture de la machine virtuelle. Les options qui ont été déconseillées, mais qui ne sont pas supprimées, produisent un avertissement de machine virtuelle.

En général, les options qui ont été supprimées n’ont aucun remplacement et le seul recours consiste à supprimer l’option de la ligne de commande. Les options de journalisation du nettoyage de la mémoire font exception à cette règle. La journalisation GC a été réappliquée dans Java 9 pour utiliser l’infrastructure de journalisation JVM unifiée. Reportez-vous au tableau 2-2 « Mapping Legacy Garbage Collection Logging Flags to the Xlog Configuration » (Mappage des indicateurs de journalisation du nettoyage de la mémoire hérités à la configuration Xlog) de la section Enable Logging with the JVM Unified Logging Framework du document de référence sur les outils Java SE 11.

Avertissements de machine virtuelle

L’utilisation d’options déconseillées génère un avertissement. Une option est déconseillée lorsqu’elle a été remplacée ou n’est plus utile. Comme pour les options supprimées, ces options doivent être supprimées de la ligne de commande. L’avertissement VM Warning: Option <option> was deprecated signifie que l’option est toujours prise en charge, mais que la prise en charge peut être supprimée ultérieurement. Option qui n’est plus prise en charge et génère l’avertissement VM Warning: Ignoring option. Les options qui ne sont plus prises en charge n’ont aucun effet sur le runtime.

L’Explorateur d’options de machine virtuelle de page web fournit une liste exhaustive des options qui ont été ajoutées ou supprimées de Java depuis JDK 7.

Erreur : Impossible de créer la machine virtuelle Java

Ce message d’erreur est imprimé lorsque la machine virtuelle JVM rencontre une option non reconnue.

AVERTISSEMENT : une opération d’accès illégale avec réflexion s’est produite.

Lorsque le code Java utilise la réflexion pour accéder à l’API interne JDK, le runtime émet un avertissement d’accès réfléchissant illégal.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int)
WARNING: Please consider reporting this to the maintainers of com.company.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Cela signifie qu’un module n’a pas exporté le package accessible via la réflexion. Le package est encapsulé dans le module et est, en fait, l’API interne. L’avertissement peut être ignoré comme première étape pour démarrer sur Java 11. Le runtime Java 11 autorise l’accès réfléchissant afin que le code hérité puisse continuer à fonctionner.

Pour résoudre cet avertissement, recherchez le code mis à jour qui n’utilise pas l’API interne. Si le problème ne peut pas être résolu avec du code mis à jour, l’option --add-exports de ligne de commande ou l’option --add-opens de ligne de commande peut être utilisée pour ouvrir l’accès au package. Ces options permettent d’accéder à des types non exportés d’un module à partir d’un autre module.

L’option --add-exports permet au module cible d’accéder aux types publics du package nommé du module source. Parfois, le code sera utilisé setAccessible(true) pour accéder aux membres non publics et à l’API. C’est ce qu’on appelle la réflexion profonde. Dans ce cas, utilisez cette option --add-opens pour accorder à votre code l’accès aux membres non publics d’un package. Si vous ne savez pas si vous souhaitez utiliser --add-export ou --add-opens, commencez par --add-export.

Les options --add-exports ou --add-opens devraient être considérées comme une solution de contournement, et non comme une solution à long terme. L’utilisation de ces options interrompt l’encapsulation du système de module, ce qui est destiné à empêcher l’utilisation de l’API interne JDK. Si l’API interne est supprimée ou modifiée, l’application échoue. L'accès par réflexion sera refusé dans Java 16, sauf si l'accès est activé via des options de ligne de commande telles que --add-opens. Pour imiter le comportement futur, définissez --illegal-access=deny sur la ligne de commande.

L’avertissement dans l’exemple ci-dessus est émis, car le sun.nio.ch package n’est pas exporté par le java.base module. En d’autres termes, il n’y a pas exports sun.nio.ch; dans le module-info.java fichier du module java.base. Cela peut être résolu avec --add-exports=java.base/sun.nio.ch=ALL-UNNAMED. Les classes qui ne sont pas définies dans un module appartiennent implicitement au module sans nom , littéralement nommées ALL-UNNAMED.

java.lang.reflect.InaccessibleObjectException

Cette exception indique que vous essayez d’appeler setAccessible(true) sur un champ ou une méthode d’une classe encapsulée. Vous pouvez également recevoir un avertissement d’accès réfléchissant illégal. Utilisez l’option --add-opens permettant de donner à votre code l’accès aux membres non publics d’un package. Le message d’exception vous indique que le module « n’ouvre pas » le package sur le module qui tente d’appeler setAccessible. Si le module est « module sans nom », utilisez-le UNNAMED-MODULE comme module cible dans l’option --add-opens .

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: 
module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6

$ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Main

java.lang.NoClassDefFoundError

NoClassDefFoundError est probablement dû à un package fractionné ou en référençant les modules supprimés.

NoClassDefFoundError provoqué par des packages fractionnés

Un paquet fractionné est lorsque un paquet se trouve dans plusieurs bibliothèques. Si une classe est introuvable alors que vous êtes certain du chemin de classe, cela peut indiquer un problème de split-package.

Ce problème se produit uniquement lors de l’utilisation du chemin d’accès au module. Le système de module Java optimise la recherche de classes en limitant un package à un module nommé . Le runtime donne la préférence au chemin d’accès du module sur le chemin d’accès à la classe lors de la recherche d’une classe. Si un package est divisé entre un module et le chemin d’accès à la classe, seul le module est utilisé pour effectuer la recherche de classe. Cela peut entraîner des NoClassDefFound erreurs.

Un moyen simple de vérifier un package fractionné consiste à insérer le chemin d'accès de votre module et le chemin de classe dans jdeps et à utiliser le chemin vers vos fichiers de classe d'application comme <chemin>. S’il existe un package fractionné, jdeps affiche un avertissement : Warning: split package: <package-name> <module-path> <split-path>.

Ce problème peut être résolu à l’aide de l’ajout --patch-module <module-name>=<path>[,<path>] du package fractionné dans le module nommé.

NoClassDefFoundError provoqué par l’utilisation de modules Java EE ou CORBA

Si l’application s’exécute sur Java 8 mais lève un java.lang.NoClassDefFoundError ou un java.lang.ClassNotFoundException, il est probable que l’application utilise un package à partir des modules Java EE ou CORBA. Ces modules ont été déconseillés dans Java 9 et supprimés dans Java 11.

Pour résoudre le problème, ajoutez une dépendance d’exécution à votre projet.

Module supprimé Package affecté Dépendance suggérée
API Java pour les services web XML (JAX-WS) java.xml.ws JAX WS RI Runtime
Architecture Java pour la liaison XML (JAXB) java.xml.bind JAXB Runtime
Framework d'Activation des JavaBeans (JAV) java.activation Framework d’activation JavaBeans (TM)
Annotations courantes java.xml.ws.annotation API d’annotation Javax
Common Object Request Broker Architecture (CORBA) java.corba GlassFish CORBA ORB
API de transaction Java (JTA) java.transaction Java Transaction API

-Xbootclasspath/p n’est plus une option prise en charge

La prise en charge de -Xbootclasspath/p a été supprimée. Utilisez --patch-module à la place. L’option --patch-module est décrite dans JEP 261. Recherchez la section intitulée « Contenu du module de mise à jour corrective ». --patch-module peut être utilisé avec javac et java pour remplacer ou augmenter les classes d’un module.

Ce que fait --patch-module , en effet, consiste à insérer le module patch dans la recherche de classe du système de module. Le système de module récupère d’abord la classe du module patch. Ceci revient à ajouter le chemin de classe de démarrage en préfixe dans Java 8.

Non pris en chargeClassVersionError

Cette exception signifie que vous essayez d’exécuter du code compilé avec une version ultérieure de Java sur une version antérieure de Java. Par exemple, vous exécutez sur Java 11 avec un fichier jar compilé avec JDK 13.

Version de Java Version du format de fichier de classe
8 52
9 53
10 54
11 55
12 56
13 57

Étapes suivantes

Une fois que l’application s’exécute sur Java 11, envisagez de déplacer des bibliothèques hors du chemin de classe et sur le chemin du module. Recherchez les versions mises à jour des bibliothèques dont dépend votre application. Choisissez des bibliothèques modulaires, le cas échéant. Utilisez le chemin d’accès au module autant que possible, même si vous ne prévoyez pas d’utiliser des modules dans votre application. L’utilisation du chemin de module offre de meilleures performances pour le chargement de classes que le chemin de classe.