Extrait d'une merveilleuse interview sur Habré: «Simon Ritter est une personne qui a travaillé sur Java depuis le tout début et continue de le faire en tant que directeur technique adjoint d'Azul, une entreprise travaillant sur la machine virtuelle Jing Zing et l'un des meilleurs ramasseurs de déchets, C4 (Continuously Concurrent Compacting Collectionneur) »
Vous trouverez ci-dessous une traduction de son article sur les nouvelles fonctionnalités de JDK 12 et certaines difficultés que vous pouvez rencontrer lors de la migration vers une nouvelle version.
J'ai écrit plusieurs articles de blog qui répertorient toutes les modifications pour chacune des dernières versions de Java ( JDK 10 , JDK 11 ). J'explorerai maintenant le côté obscur de JDK 12, en me concentrant sur certains des pièges qui peuvent poser des problèmes si vous souhaitez porter l'application sur cette version.

JDK 12 possède le plus petit nombre de nouvelles fonctionnalités de toutes les versions Java à ce jour (j'en ai compté 109 dans JDK 10 et 90 dans JDK 11). Ce n'est pas mal - en raison des cycles de publication, certaines versions contiendront plus de changements et d'autres moins.
Je décomposerai de nouvelles fonctions en domaines logiques évidents: Java, bibliothèques, JVM et autres fonctions JDK.
Changements de langue
La fonction que je (et je suppose que beaucoup d'autres personnes) considéreront la plus visible dans JDK 12 est la nouvelle instruction switch ( JEP 325 ). Il s'agit également du premier changement de langue à utiliser comme fonction de «prévisualisation». L'idée de «prévisualisation» a été introduite début 2018 dans le cadre de JEP 12 . Il s'agit essentiellement d'un moyen d'activer les versions bêta de nouvelles fonctionnalités à l'aide des options de ligne de commande. En utilisant l'aperçu, il est toujours possible d'effectuer des modifications en fonction des commentaires des utilisateurs et, dans le pire des cas, de supprimer complètement une fonction si elle n'a pas été reçue correctement. La clé des fonctions d'aperçu est qu'elles ne sont pas incluses dans la spécification Java SE. À propos du nouveau commutateur, il y a une très bonne traduction sur Habré.
Dans JDK 12, un commutateur est devenu une expression qui évalue son «contenu» pour produire un résultat. J'expliquerai tout de suite que cela n'affecte pas la compatibilité descendante, vous n'avez donc pas besoin de changer de code utilisant Switch comme opérateur.
Je vais utiliser l'exemple de JEP, car il est simple et clair:
Ancien interrupteurint numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Huh? " + day); }
Comme vous pouvez le voir, nous mappons le jour de la semaine au nom de la variable day
, puis numLetters
la valeur numLetters
. Maintenant que switch est un opérateur, nous pouvons effectuer l'affectation une fois (ce qui réduit considérablement la probabilité d'un code erroné) en utilisant le résultat de l'instruction switch:
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Huh? " + day); };
Vous remarquerez rapidement deux changements de syntaxe. Les développeurs d'OpenJDK sont tombés sur une fonction de syntaxe peu connue appelée liste séparée par des virgules. L'opérateur d'expression lambda ->
facilite également le retour de la valeur. Vous pouvez toujours utiliser break
avec une valeur si vous le voulez vraiment. Il existe plusieurs autres détails sur cette fonctionnalité, mais il est probablement plus facile de lire JEP.
Bibliothèques
Il y a un changement que je trouve très utile. Il en existe également plusieurs.
collecteur de départ
L'API Streams, comme d'habitude, a un nouveau collecteur, fourni par la classe utilitaire Collectors. Un nouveau collecteur peut être obtenu en utilisant la méthode teeing()
. Le collecteur de départs prend trois arguments: deux collecteurs et une bifonction. Pour comprendre le travail de ce collectionneur, je recommande cet article sur Habré .
Pour comprendre comment il fait ça, j'ai dessiné un schéma:

Toutes les valeurs du flux d'entrée sont transmises à chaque collecteur. Le résultat de chaque collecteur est transmis en tant qu'arguments à BiFunction pour générer le résultat final.
Un exemple simple est le calcul de la valeur moyenne (oui, je sais qu'il existe déjà des collecteurs pour cela, comme averagingInt()
, mais ceci est un exemple simple pour aider à comprendre le concept).
double average = Stream.of(1, 4, 2, 7, 4, 6, 5) .collect(teeing( summingDouble(i -> i), counting(), (sum, n) -> sum / n) );
Le premier collecteur calcule la somme du flux d'entrée, et le second - le nombre d'éléments. BiFunction divise la somme par le nombre d'éléments pour obtenir la valeur moyenne.
java.io
InputStream skipNBytes(long n)
- ignore et supprime exactement n octets du flux d'entrée InputStream. Si n est égal ou inférieur à zéro, les octets ne sont pas ignorés.
java.lang
Un nouveau package est apparu, java.lang.constant, qui fait partie de l'API JVM constante, JEP 334 .
Chaque fichier de classe Java possède un pool persistant qui stocke des opérandes pour les instructions de bytecode dans la classe. Il est difficile pour les développeurs de manipuler les fichiers de classe en raison de problèmes de chargement des classes. L'API JVM constante fournit des types de référence symboliques pour décrire chaque forme d'une constante (classe, constante chargeable, MethodHandle
, constante MethodHandle
, constante MethodType
).
Il a également influencé plusieurs autres classes. Toutes les classes suivantes ont désormais une méthode describeConstable()
:
- Classe
- Double
- Enum
- Flotter
- Entier
- Long
- String
- Methodhandle
- MethodType
- Varhandle
En tant que britannique, je trouve ça assez drôle. Le terme Constable, describeConstable
utilisé depuis le XIe siècle, et c'est ainsi que nous nous référons souvent aux policiers. C'est aussi le nom du célèbre artiste du XVIIIe siècle, John Constable. Cela me fait me demander si la méthode describeTurner()
sera dans une future version. Évidemment, dans ce cas, il s'agit d'une abréviation de la Constant Table
, sans lien avec un avocat ou un paysagiste.
Les classes suivantes incluent désormais la méthode resolveConstantDesc()
:
- Double
- Enum.EnumDesc
- Flotter
- Entier
- Long
- String
java.lang.Character
Les classes internes ont été mises à jour pour inclure de nouveaux blocs Unicode. J'aime toujours voir ce que les gens ont trouvé à ajouter à Unicode, voici quelques exemples:
- Symboles d'échecs
- Numéros mayas
- Le sogdian est une langue iranienne orientale qui n'était plus utilisée au 11e siècle.
- Old Sogdian est une version plus ancienne (et, je suppose, encore plus limitée) de Sogdian
java.lang.Class
arrayType()
renvoie Class
pour le type du tableau dont le type de composant est décrit par cette Class
. Cela peut être vérifié à l'aide de jshell
:
jshell> (new String[2]).getClass().getName() $11 ==> "[Ljava.lang.String;" jshell> (new String[2]).getClass().arrayType() $12 ==> class [[Ljava.lang.String; jshell> "foo".getClass().arrayType() $15 ==> class [Ljava.lang.String;
Je ne suis pas tout à fait sûr de la signification de cette méthode, car elle ne fait qu'ajouter une Class
au type que cette classe représente.
componentType()
, identique à getComponentType()
. La question se pose - pourquoi ajouter une méthode redondante?
descriptorString()
- renvoie à nouveau le même résultat que getName()
. Cependant, cela est nécessaire car Class
implémente désormais l'interface TypeDescriptor
associée à la nouvelle API JVM constante.
lava.lang.String
indent()
- Ajoute une série d'espaces de début à une chaîne. Si le paramètre est négatif, ce nombre d'espaces de tête sera supprimé (si possible).
transform()
- Applique la fonction fournie à une chaîne. Le résultat peut ne pas être une chaîne.
java.lang.invoke
VarHandle
maintenant toString()
pour renvoyer une description compacte.
java.net.SecureCacheResponse
et java.net.ssl.HttpsConnection
ont une nouvelle méthode, getSSLSession()
qui renvoie Optional
contenant la SSLSession
utilisée dans la connexion.
java.nio.files
La classe Files
possède une nouvelle méthode, mismatch()
, qui recherche et renvoie la position du premier octet de non-concordance dans le contenu de deux fichiers, ou -1L s'il n'y a pas de non-concordance.
java.text
Il existe une nouvelle classe CompactNumberFormat
. Il s'agit d'une sous-classe de NumberFormat
qui formate un nombre décimal sous forme compacte. Un exemple de formulaire compact - 1M
au lieu de 1000000
, donc - nécessite deux au lieu de neuf caractères. NumberFormat
et java.text.spi.NumberFormatProvider
ont été étendus pour inclure la nouvelle méthode getCompactNumberInstance()
. Il existe également une nouvelle énumération, NumberFormatStyle
qui a deux significations: LONG et SHORT.
java.util.concurrent
CompletionStage comprend désormais plusieurs formulaires surchargés avec trois méthodes:
- exceptionnellement asynchrone
- exceptionnellement
- exceptionnellementComposeAsync
Ces méthodes élargissent les possibilités de création d'un nouveau CompletionStage
partir d'un existant, CompletionStage
si l'actuel se termine par une exception. Consultez la documentation de l'API pour plus de détails.
javax.crypto
La classe Cipher
a une nouvelle toString()
qui renvoie une chaîne contenant la transformation, le mode et le fournisseur Cipher
.
javax.naming.ldap.spi
Il s'agit d'un nouveau package dans JDK 12 et il contient deux classes: LdapDnsProvider
, qui est la classe de fournisseur pour les recherches DNS pendant les opérations LDAP, et LdapDnsProviderResults
qui encapsule le résultat de la recherche DNS pour l'URL LDAP.
Swing
Swing est toujours en cours de mise à jour! Oui, filechooser.FileSystemView
dispose désormais d'une nouvelle méthode getChooserShortcutPanelFiles()
. Il renvoie un tableau de fichiers représentant les valeurs à afficher par défaut dans la barre de raccourcis de sélection de fichiers.
Modifications de la JVM
Shenandoah est un projet de recherche annoncé par Red Hat en 2014 qui se concentre sur les exigences des applications à faible latence pour la gestion de la mémoire dans la JVM. Ses objectifs sont un temps de pause maximal de 1 à 10 ms pour un tas de plus de 20 Go ( il n'est donc pas destiné aux petites applications - comme l' un des développeurs de Shenandoah a répondu , ce n'est pas le cas et il fait un excellent travail avec de petites applications). Ce collecteur est conçu pour fonctionner en parallèle avec les threads d'application, évitez donc les problèmes que nous voyons dans la plupart des récupérateurs de place.
Cette modification vise à améliorer le comportement du collecteur G1 lorsqu'il atteint l'objectif de retard défini. G1 divise l'espace de mémoire (ancien et ancien) en régions. L'idée est que dans l'ancienne génération, vous n'avez pas besoin de collecter les déchets en une seule opération. Lorsque G1 doit collecter des ordures, il sélectionne les régions qu'il définit. C'est ce qu'on appelle un kit de collecte. Avant JDK 12, lorsque les travaux ont commencé sur le plateau, tous les travaux devaient être achevés, essentiellement, comme une opération atomique. Le problème était que, parfois, en raison de changements dans l'utilisation de l'espace de tas par l'application, l'ensemble de collecte s'avérait trop volumineux et prenait trop de temps à collecter, ce qui conduisait au fait que le temps de pause n'était pas atteint.
Dans JDK 12, si G1 identifie cette situation, il interrompra la collecte de données à mi-chemin si cela n'affecte pas la capacité de l'application à continuer d'allouer de l'espace pour de nouveaux objets. L'effet net de G1 sera meilleur lorsqu'un temps de pause court sera atteint.
Il s'agit d'une autre amélioration des performances pour G1, mais une autre est liée à la façon dont la JVM interagit avec le reste du système. De toute évidence, la mémoire est requise pour le tas JVM et, au démarrage, il demande de la mémoire à l'allocateur de mémoire virtuelle du système d'exploitation. Lorsque l'application démarre, il peut arriver que la quantité de mémoire requise pour le segment de mémoire diminue et qu'une partie de la mémoire allouée puisse être renvoyée au système d'exploitation pour être utilisée par d'autres applications.
G1 le fait déjà, mais ne peut le faire qu'à deux endroits. D'une part, lors d'une collection complète, et d'autre part, lors d'un des cycles parallèles. G1 essaie de ne pas effectuer de collecte complète et avec une faible utilisation de la mémoire, il peut y avoir des périodes importantes entre les cycles de collecte. Cela conduit au fait que G1 peut conserver une mémoire fixe pendant une longue période.
Dans JDK 12, G1 essaiera périodiquement de continuer ou d'exécuter une boucle parallèle pendant que l'application est inactive pour déterminer l'utilisation globale du tas Java. La mémoire inutilisée peut être retournée au système d'exploitation de manière plus rapide et prévisible.
Le nouvel indicateur de ligne de commande -XX:G1PeriodicGCInterval
peut être utilisé pour définir le nombre de millisecondes entre les vérifications.
Cette fonctionnalité entraînera une utilisation plus conservatrice de la mémoire JVM pour les applications qui ont été inactives pendant de longues périodes.
Autres nouvelles fonctionnalités JDK
Java Microbenchmarking Harness (JMH) a été développé par Alexey Shipilev lorsqu'il travaillait chez Oracle et fournit une plate-forme étendue pour développer des tests de performances pour les applications Java. Alexey a fait un travail remarquable en aidant les gens à éviter de nombreuses erreurs simples qu'ils font en essayant d'analyser les performances des applications: échauffement, éviter les exceptions, etc.
Maintenant, JMH peut être inclus dans OpenJDK. Quiconque souhaite travailler sur le JDK lui-même et changer le code peut l'utiliser pour comparer les performances avant et après leurs modifications, ainsi que pour comparer les performances dans différentes versions. Un certain nombre de tests sont inclus pour permettre les tests; La conception de JMH est telle qu'il est facile d'ajouter de nouveaux tests là où cela est nécessaire.
OpenJDK possède deux ports pour l'architecture Arm64, l'un fourni par Oracle et l'autre par Red Hat. Comme cela n'était pas nécessaire et qu'Oracle a cessé de prendre en charge Arm pour ses binaires JDK, il a été décidé d'utiliser uniquement le port Red Hat, qui est toujours pris en charge et développé.
La classe de partage de données (CDS) était auparavant une fonctionnalité commerciale d'Oracle JDK. Avec une transition récente effectuée dans JDK 11 pour éliminer toutes les différences fonctionnelles entre Oracle JDK et OpenJDK, il a été inclus dans OpenJDK.
Pour utiliser CDS, vous avez besoin d'une archive créée pour les classes qui se chargent au démarrage de l'application. JDK 12 pour les plates-formes 64 bits a désormais le fichier classes.jsa
dans le répertoire lib/server
. Il s'agit de l'archive CDS pour les «classes par défaut». Je suppose que cela signifie toutes les classes publiques dans les modules JDK; Je n'ai pas trouvé de moyen de le déballer pour vérifier. Étant donné que CDS est activé par défaut, ce qui équivaut à l'option -Xshare:auto
sur la ligne de commande, les utilisateurs bénéficieront de temps de démarrage de l'application améliorés.
Conclusions
JDK 12 fournit un petit nombre de nouvelles fonctions et API, la switch
étant la plus intéressante pour les développeurs. Les utilisateurs de G1 apprécieront certainement les améliorations de performances.
Avec la nouvelle version de la version, je conseillerais à tous les utilisateurs de tester leurs applications dans cette version. Le suivi des modifications incrémentielles vous aidera à éviter les surprises si vous décidez de passer à la prochaine version du support à long terme.
Nous avons des versions JDK 12 gratuites pour Zulu Community Edition pour vous aider avec vos tests. Assurez-vous de les essayer.