
En fin d’année dernière, l'équipe Java Platform au Groupe Oracle a livré une Preview des nouveautés du JDK 18. Aujourd’hui, Java 18 est livré avec une API pour les vecteurs, un deuxième aperçu de la correspondance des motifs pour les instructions switch, l'UTF-8 comme jeu de caractères par défaut et d'un serveur Web minimal.
Réimplémentation de Core Reflection avec des gestionnaires de méthode
La réflexion de base a deux mécanismes internes pour invoquer les méthodes et les constructeurs. Pour un démarrage rapide, il utilise les méthodes natives de la VM HotSpot pour les premières invocations d'une méthode réflective spécifique ou d'un objet constructeur. Pour une meilleure performance maximale, après un certain nombre d'invocations, il génère du bytecode pour l'opération de réflexion et l'utilise lors des invocations suivantes.
Réimplémente les éléments java.lang.reflect.Method, Constructor et Field sur les gestionnaires de méthode java.lang.invoke. En faisant des gestionnaires de méthodes le mécanisme sous-jacent de la réflexion, les developpeurs reduisent les coûts de maintenance et de développement des API java.lang.reflect et java.lang.invoke.
Pour l'accès aux champs, la réflexion de base utilise l'API interne sun.misc.Unsafe. Avec l'API java.lang.invoke method-handle introduite dans Java 7, il existe en tout trois mécanismes internes différents pour les opérations de réflexion :
- les méthodes natives VM ;
- les gestionnaires de méthode ;
- les stubs de bytecode générés dynamiquement pour Method::invoke et Constructor::newInstance, ainsi que l'accès non sécurisé aux champs pour Field::get et set.
Lorsque java.lang.reflect et java.lang.invoke sont mise à jour pour prendre en charge de nouvelles fonctionnalités du langage, comme celles envisagées dans le projet Valhalla, les trois chemins de code doivent être modifié, ce qui est coûteux. En outre, l'implémentation actuelle repose sur un traitement spécial par la VM du bytecode généré, qui est enveloppé dans des sous-classes de jdk.internal.reflect.MagicAccessorImpl :
- La vérification est désactivée pour contourner JLS §6.6.2 afin de supporter la réflexion sur Object::clone ;
- Un chargeur de classe non conforme est utilisé pour contourner certains problèmes de sécurité et de compatibilité ;
- L'accessibilité est relâchée afin que ces classes puissent accéder aux champs et méthodes inaccessibles d'autres classes.
Description
La nouvelle implémentation effectue des invocations directes des poignées de méthodes pour des objets réfléchissants spécifiques. L’équipe Java Platform du Groupe Oracle utilise le mécanisme de réflexion natif de la VM uniquement au début du démarrage de la VM, avant l'initialisation du mécanisme de gestion des méthodes. Cela se produit peu après System::initPhase1 et avant System::initPhase2, après quoi nous passons à l'utilisation exclusive des method-handle. Cela profite au projet Loom en réduisant l'utilisation de cadres de pile natifs
Réimplémentation de java.lang.reflect sur les method handles en tant que mécanisme réflectif sous-jacent commun de la plate-forme en remplaçant les implémentations génératrices de bytecode de Method::invoke, Constructor::newInstance, Field::get et Field::set.
.
Pour des performances optimales, les instances de Method, Constructor et Field doivent être maintenues dans des champs statiques finaux afin qu'elles puissent être repliées en permanence par le JIT. Lorsque cela est fait, les microbenchmarks montrent que les performances de la nouvelle implémentation sont nettement plus rapides que celles de l'ancienne, de 43 à 57 %.
Lorsque les instances de méthode, de constructeur et de champ sont maintenues dans des champs non constants (par exemple, dans un champ non final ou un élément de tableau), les microbenchmarks montrent une certaine dégradation des performances. La performance des accès aux champs est significativement plus lente que l'ancienne implémentation, de 51-77 %, lorsque les instances Field ne peuvent pas être pliées de manière constante.
Cette dégradation peut toutefois ne pas avoir beaucoup d'effet sur les performances des applications du monde réel. L’équipe Java Platform du Groupe Oracle a effectué plusieurs tests de sérialisation et de désérialisation à l'aide de bibliothèques du monde réel et n’a constaté aucune dégradation des performances.
- un benchmark de sérialiseur de champ Kryo ;
- Un benchmark de type convertisseur XStream ;
- Un benchmark personnalisé de sérialisation et désérialisation JSON utilisant Jackson.
L’équipe Java Platform du Groupe Oracle promet de continuer à explorer les possibilités d'améliorer les performances, par exemple en affinant les formes de bytecode pour l'accès aux champs afin de permettre aux MethodHandles et VarHandles concrets d'être optimisés de manière fiable par le JIT, que le récepteur soit constant ou non. La nouvelle implémentation réduira le coût de la mise à niveau du support de réflexion pour les nouvelles fonctionnalités du langage et, en outre, permettra à l’équipe Java Platform de simplifier la VM HotSpot en supprimant le traitement spécial des sous-classes MagicAccessorImpl.
Risques et hypothèses
Le code qui dépend d'aspects hautement spécifiques et non documentés de l'implémentation existante peut être affecté. Pour atténuer ce risque de compatibilité, les développeurs Java peuvent activer l'ancienne implémentation via -Djdk.reflect.useDirectMethodHandle=false.
Le code qui inspecte les classes de réflexion internes générées (c'est-à-dire les sous-classes de MagicAccessorImpl) ne fonctionnera plus et devra être mis à jour.
L'invocation de Method-handle peut consommer plus de ressources que l'ancienne implémentation de base de la réflexion. Une telle invocation implique l'appel de plusieurs méthodes Java pour s'assurer que la classe déclarante d'un membre est initialisée avant l'accès, et peut donc nécessiter plus d'espace de pile pour les cadres d'exécution nécessaires. Cela peut entraîner une StackOverflowError ou, si une StackOverflowError est lancée lors de l'initialisation d'une classe, une NoClassDefFoundError.
L’équipe Java Platform supprimera l'ancienne implémentation de base de la réflexion dans une prochaine version. La solution de contournement -Djdk.reflect.useDirectMethodHandle=false ne fonctionnera plus à ce moment-là.
SPI de résolution d'adresses Internet
Cette amélioration définit une interface de fournisseur de services (SPI) pour la résolution des noms et adresses d'hôtes, de sorte que java.net.InetAddress puisse utiliser des résolveurs autres que le résolveur intégré à la plateforme. L'API java.net.InetAddress résout les noms d'hôtes en adresses IP (Internet Protocol), et vice versa. L'API utilise actuellement le résolveur natif du système d'exploitation, qui est généralement configuré pour utiliser une combinaison d'un fichier d'hôtes local et du système de noms de domaine (DNS). Les motivations pour définir une interface de fournisseur de services pour la résolution de noms et d'adresses incluent :
- Projet Loom : une opération de résolution avec l'API InetAddress se bloque actuellement dans un appel du système d'exploitation. C'est un problème pour les threads virtuels en mode utilisateur de Loom, car cela empêche les threads de la plate-forme sous-jacente de servir d'autres threads virtuels en attendant la fin d'une opération de résolution. Un autre résolveur pourrait mettre en œuvre le protocole client DNS directement, sans blocage ;
- Protocoles réseau émergents : un résolveur SPI permettrait l'intégration transparente de nouveaux protocoles de résolution tels que le DNS sur QUIC, TLS ou HTTPS ;
- Personnalisation : un résolveur SPI permettrait aux cadres et aux applications d'avoir un contrôle plus fin sur les résultats de résolution, et permettrait aux bibliothèques existantes d'être équipées d'un résolveur personnalisé ;
- Test : les activités de prototypage et de test nécessitent souvent le contrôle des résultats de la résolution des noms d'hôtes et des adresses, par exemple lors de la simulation de composants qui utilisent l'API InetAddress.
Description
L'API InetAddress définit plusieurs méthodes pour les opérations de consultation :
- InetAddress::getAllByName effectue une recherche vers l'avant, en associant un nom d'hôte à un ensemble d'adresses IP ;
- InetAddress::getByName effectue également une recherche avant, en associant un nom d'hôte à la première adresse de son ensemble d'adresses ;
- InetAddress::getCanonicalHostName effectue une recherche inverse, en faisant correspondre une adresse IP à un nom de domaine entièrement qualifié ;
Par exemple :
Code : Sélectionner tout 1
2
3var addressBytes = new byte[] { (byte) 192, 0, 43, 7} ; var resolvedHostName = InetAddress.getByAddress(addressBytes) .getCanonicalHostName() ;
- InetAddress::getHostName effectue également une recherche inverse, si nécessaire.
- Par défaut, InetAddress utilise le résolveur natif du système d'exploitation pour effectuer les recherches. Le résultat de cette recherche, qu'il soit positif ou négatif, peut être mis en cache afin d'éviter d'autres recherches sur le même hôte.
Interface du fournisseur de services
L'API InetAddress utilise un chargeur de services pour localiser un fournisseur de résolveur. Si aucun fournisseur n'est trouvé, l'implémentation intégrée sera utilisée comme auparavant. Les nouvelles classes du paquet java.net.spi sont :
- InetAddressResolverProvider : une classe abstraite définissant le service à localiser par java.util.ServiceLoader. Un InetAddressResolverProvider est, essentiellement, une usine pour les résolveurs. Le résolveur instancié sera défini comme le résolveur du système, et InetAddress déléguera toutes les demandes de recherche à ce résolveur ;
- InetAddressResolver : une interface qui définit les méthodes pour les opérations fondamentales de recherche en avant et en arrière. Une instance de cette interface est obtenue à partir d'une instance de InetAddressResolverProvider ;
- InetAddressResolver.LookupPolicy : classe dont les instances décrivent les caractéristiques d'une demande de résolution, notamment le type d'adresse demandé et l'ordre dans lequel les adresses doivent être renvoyées ;
- InetAddressResolverProvider.Configuration : interface décrivant la configuration intégrée de la plate-forme pour les opérations de résolution. Elle permet d'accéder au nom d'hôte local et au résolveur intégré. Elle est utilisée par les fournisseurs de résolveur personnalisés pour amorcer la construction du résolveur ou pour mettre en œuvre la délégation partielle des demandes de résolution au résolveur natif du système d'exploitation.
Alternatives
Sans un SPI tel que celui proposé ici, les applications devront continuer à utiliser les solutions de contournement disponibles aujourd'hui.
Une application peut utiliser l'interface Java Naming and Directory (JNDI) et son fournisseur DNS pour rechercher les noms et adresses du réseau. Cette approche peut être utile pour les applications qui nécessitent un contrôle précis des recherches DNS, mais elle est découplée d'InetAddress et son utilisation avec l'API de mise en réseau de la plateforme nécessite des efforts supplémentaires.
Une application peut utiliser les bibliothèques de résolveurs du système d'exploitation directement via l'interface Java Native (JNI) ou l'API de fonction étrangère du projet Panama. Cependant, comme pour JNDI, cette approche est découplée d'InetAddress et donc plus difficile à utiliser.
Une application peut également utiliser la propriété système jdk.net.hosts.file, non standard et spécifique au JDK, pour configurer InetAddress afin qu'il utilise un fichier spécifique, plutôt que le résolveur natif du système d'exploitation, pour mapper les noms d'hôtes en adresses IP. Cette fonctionnalité est utile pour les tests, mais elle ne constitue pas une solution générale, car la liste complète des noms d'hôtes n'est pas toujours connue à l'avance.
L’équipe Java Platform développe de nouveaux tests pour le SPI résolveur. Elle développe des fournisseurs de résolveurs de preuve de concept pour démontrer et vérifier que le SPI peut être utilisé pour développer et déployer des implémentations alternatives qui sont utilisées de préférence à l'implémentation intégrée du JDK. « Nous mettrons au moins un de ces fournisseurs à disposition pour amorcer le développement d'implémentations plus complètes », déclare l’équipe Java.
Serveur Web simple et prêt à l'emploi
Il fournit un outil en ligne de commande pour démarrer un serveur web minimal qui sert uniquement des fichiers statiques. Aucune fonctionnalité de type CGI ou servlet n'est disponible. Cet outil sera utile pour le prototypage, le codage ad-hoc et les tests, en particulier dans les contextes éducatifs.
- réduire l'énergie d'activation des développeurs et rendre le JDK plus accessible ;
- Offrir un serveur de fichiers HTTP statiques prêt à l'emploi avec une configuration facile et une fonctionnalité minimale ;
- fournir une implémentation par défaut via la ligne de commande ainsi qu'une petite API pour la création et la personnalisation programmatique.
Un rite de passage commun pour les développeurs est de servir un fichier sur le web, probablement un fichier "Hello, world ! un fichier HTML. La plupart des programmes d'enseignement de l'informatique initient les étudiants au développement Web, où des serveurs de test locaux sont couramment utilisés. En général, les développeurs apprennent également l'administration système et les services Web, d'autres domaines où les outils de développement dotés de fonctionnalités serveur de base peuvent s'avérer utiles. Les tâches éducatives et informelles de ce type sont celles pour lesquelles un petit serveur prêt à l'emploi est souhaitable. Les cas d'utilisation comprennent :
- les tests de développement Web, où un serveur de test local est utilisé pour simuler une configuration client-serveur ;
- La navigation informelle et le partage de fichiers entre systèmes pour, par exemple, rechercher un répertoire sur un serveur distant depuis votre machine locale ;
- les tests de services ou d'applications Web, où des fichiers statiques sont utilisés en tant que socles d'API dans une structure de répertoire qui reflète les URL RESTful et contient des données factices.
« Dans tous ces cas, nous pouvons, bien sûr, utiliser un framework de serveur web, mais cette approche a une énergie d'activation élevée : nous devons rechercher des options, en choisir une, la télécharger, la configurer et comprendre comment l'utiliser avant de pouvoir répondre à notre première requête », indique l’équipe Java.
« Ces étapes représentent un certain nombre de cérémonies, ce qui est un inconvénient ; rester bloqué en cours de route peut être frustrant et peut même entraver l'utilisation ultérieure de Java. Un serveur web de base lancé à partir de la ligne de commande ou via quelques lignes de code nous permet d'éviter cette cérémonie et de nous concentrer sur la tâche à accomplir », ajoutent-ils. Python, Ruby, PHP, Erlang et bien d'autres platesformes proposent des serveurs prêts à l'emploi fonctionnant en ligne de commande. Cette variété d'alternatives existantes démontre un besoin reconnu pour ce type d'outil.
Description
Le mini Server Web est un serveur HTTP minimal destiné à servir une seule hiérarchie de répertoires. Il est basé sur l'implémentation du serveur web dans le paquetage com.sun.net.httpserver qui est inclus dans le JDK depuis 2006. Le paquetage est officiellement pris en charge, et l'équipe Java Platform au Groupe Oracle l'étend avec des API pour simplifier la création de serveurs et améliorer le traitement des requêtes. Le mini Serveur Web peut être utilisé via l'outil de ligne de commande dédié jwebserver ou de manière programmatique via son API.
Outil de ligne de commande
La commande suivante démarre le mini server Web :
Code : | Sélectionner tout |
$ jwebserver
Code : | Sélectionner tout |
$ jwebserver
Servir /cwd et ses sous-répertoires sur 127.0.0.1 port 8000
URL : http://127.0.0.1:8000/
Par défaut, le serveur fonctionne en avant-plan et se lie à l'adresse de bouclage et au port 8000. Ceci peut être modifié avec les options -b et -p. Par exemple, pour exécuter le serveur sur le port 9000, utilisez :
Code : | Sélectionner tout |
$ jwebserver -p 9000
Code : | Sélectionner tout |
$ jwebserver -b 0.0.0.0
URL: http://123.456.7.891:8000/
Par défaut, les fichiers sont servis à partir du répertoire courant. Un répertoire différent peut être spécifié avec l'option -d.
Seules les requêtes HEAD et GET idempotentes sont servies. Toute autre demande reçoit une réponse 501 - Not Implemented ou 405 - Not Allowed. Les requêtes GET sont mises en correspondance avec le répertoire servi, comme suit :
- Si la ressource demandée est un fichier, son contenu est servi.
- Si la ressource demandée est un répertoire qui contient un fichier d'index, c'est le contenu de ce dernier qui est servi.
- Sinon, les noms de tous les fichiers et sous-répertoires du répertoire sont listés. Les liens symboliques et les fichiers cachés ne sont pas répertoriés ni servis.
Le mini serveur Web prend en charge le protocole HTTP/1.1 uniquement. Il n'y a pas de support HTTPS. Les types MIME sont configurés automatiquement. Par exemple, les fichiers .html sont servis en tant que text/html et les fichiers .java sont servis en tant que text/plain. Par défaut, chaque requête est enregistrée sur la console. La sortie ressemble à ceci :
127.0.0.1 - - [10/Feb/2021:14:34:11 +0000] "GET /some/subdirectory/ HTTP/1.1" 200 -
L'option -h affiche un message d'aide listant toutes les options, qui suivent les directives de la JEP 293. Une page de manuel jwebserver est également disponible....
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.