Java 16 : quoi de neuf ?

Java 16 : quoi de neuf ?

Maintenant que Java 16 est features complete (Rampdown Phase Two au jour d’écriture de l’article), c’est le moment de faire le tour des fonctionnalités qu’apporte cette nouvelle version, à nous, les développeurs.

Cet article fait partie d’une suite d’article sur les nouveautés des dernières versions de Java, pour ceux qui voudraient les lire en voici les liens : Java 15, Java 14, Java 13, Java 12, Java 11Java 10, et Java 9.

Cette nouvelle version compte pas moins de 17 JEP mais surtout voit les Records et le pattern matching pour instanceof sortir de preview et ça c’est une bonne nouvelle !

JEP 380: Unix-Domain Socket Channels

Tout d’abord une petite description de ce qu’est un socket de type Unix-Domain :

Les sockets Unix-Domain sont utilisés pour la communication inter-processus (IPC) sur le même hôte. Ils sont similaires aux sockets TCP/IP à bien des égards, sauf qu’ils sont adressés par path de filesystem plutôt que par des adresses IP et des numéros de port.

Les classes SocketChannel et ServerSocketChannel peuvent dorénavant être créées depuis un socket de type Unix-Domain.

Exemple (non testé):

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(UnixDomainSocketAddress.of("path/to/socket/file");
while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    //do something with socketChannel...
}

Plus d’info : JEP-380.

JEP 338: Vector API (Incubator)

Vector API est une nouvelle API qui permet d’exprimer des calculs de vecteur (calcul matriciel entre autres), qui seront exécutés via des instructions machines optimales en fonction de la plateforme d’exécution.
Ces optimisations incluent des changements au sein du Just In Time compiler, des intrinsics, et utilisent les instructions AVX/SSE des CPU qui permettent vectorisation des calculs (instructions de type SIMD – Single Instruction Multiple Data).

La JEP contient l’exemple suivant, implémenté avant la Vector API par :

void scalarComputation(float[] a, float[] b, float[] c) {
   for (int i = 0; i < a.length; i++) {
        c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
   }
}

Et avec la Vector API par :

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorComputation(float[] a, float[] b, float[] c) {

    for (int i = 0; i < a.length; i += SPECIES.length()) {
        var m = SPECIES.indexInRange(i, a.length);
    // FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i, m);
        var vb = FloatVector.fromArray(SPECIES, b, i, m);
        var vc = va.mul(va).
                    add(vb.mul(vb)).
                    neg();
        vc.intoArray(c, i, m);
    }
}

On utilise ici un FloatVector.SPECIES_256 qui permet de gérer des vecteurs de float sur 256 bits. Les opérations mul, add et neg seront donc faites via des instructions SIMD sur 256 bits au lieu d'être faites unitairement sur chaque float.

Plus d'info : JEP-338.

JEP 389: Foreign Linker API (Incubator)

Avec la JEP-393 Foreign-Memory API, qui permet de gérer des segments mémoire (on heap ou off heap), cette nouvelle fonctionnalité pose les bases du projet Panama en permettant l’interconnexion de la JVM avec du code natif.

La Foreign Linker API permet d'appeler du code natif (en C par exemple) de façon facile et performante.

Voici un exemple d'appel de la fonction strlen de la librairie standard C :

MethodHandle strlen = CLinker.getInstance().downcallHandle(
        LibraryLookup.ofDefault().lookup("strlen").get(),
        MethodType.methodType(long.class, MemoryAddress.class),
        FunctionDescriptor.of(C_LONG, C_POINTER)
);

long len = strlen.invokeExact(CLinker.toCString("Hello").address())

Plus d'informations sur les appels de fonction native dans cet article de Maurizio Cimadamore : State of foreign function support.

Ce code peut sembler un peu complexe, c'est pour cela qu'a été créé jextract, qui permet d'extraire le code nécessaire à l'appel d'une librairie C automatiquement depuis un fichier header C.

Exemple pour l'appel de la méthode getpid de la librairie standard C :

echo "int getpid();" > getpid.h
jextract -t com.unix getpid.h
import static com.unix.getpid_h.*;

class Main2 {
   public static void main(String[] args) {
      System.out.println(getpid());
  }
}

Plus d'informations sur jextract dans cet article, de Sundar Athijegannathan : Project Panama and jextract.

Deux nouveaux ports de la JVM

OpenJDK 16 ajoute le support Alpine Linux et donc Musl comme implémentation de la librairie standard C pour les architectures x64 et AArch64. Plus d'informations dans la JEP-386.

OpenJDK 16 ajoute aussi le support de l'architecture AArch64 sous Windows (précédemment uniquement supporté sous Linux) via la JEP 388. Le support de macOS sur cette architecture est en cours et sera certainement livré dans une prochaine version de Java, voir la JEP 391.

Les fonctionnalités qui passent de preview à standard

Les fonctionnalités suivantes, qui étaient en preview (ou en incubator module), sont maintenant en standard.
Pour les détails sur celles-ci vous pouvez vous référer à mes articles précédents.

Les fonctionnalités qui restent en preview

Les fonctionnalités suivantes restent en preview (ou en incubator module).
Pour les détails sur celles-ci vous pouvez vous référer à mes articles précédents.

  • JEP 393 : Foreign-Memory Access API (Third Incubator)
  • JEP 397 : Sealed Classes (Second Preview)

Divers

Divers ajouts au JDK :

  • JDK-8238286 : Stream.mapMulti() : permet de mapper un élément T en n éléments R via un Consumer<R>
  • JDK-8180352 : Stream.toList() : accumule les éléments de la Stream dans une liste immuable.
  • JDK-8255150 : Ajout de méthodes utilitaires pour vérifier le range d'un index de type long : Objects.checkIndex(long index, long length), Objects.checkFromToIndex(long fromIndex, long toIndex, long length), Objects.checkFromIndexSize(long fromIndex, long size, long length). Ces nouvelles méthodes sont optimisées via l'ajout d'intrinsics.

Lors de la modularisation du JDK via le projet Jigsaw, certaines API internes du JDK qui ne devraient pas être utilisables en dehors de celui-ci, ont quand même été rendues utilisables (par manque d'alternatives, ou pour donner aux applications les utilisant un temps de migration). C'est ce qui a été appelé Relaxed Strong Encapsulation. Un simple WARNING était alors affiché dans les logs de la JVM lors de la première utilisation de ces API (comportement modifiable via --illegal-access). Avec la JEP 396 : Strongly Encapsulate JDK Internals by Default, l'accès à ces API (qui ont quasiment toutes un remplacement officiel dans le JDK), est maintenant interdit par défaut. La valeur par défaut du flag --illegal-access passe donc de permit à deny. Le flag étant toujours présent on peut le modifier pour retrouver le comportement précédent si nécessaire.

Un changement en préparation des inline classes du projet Valhalla : JEP 390 : Warnings for Value-Based Classes. Certaines classes du JDK sont appelées Value-Based Classes, ce sont des classes qui ne sont que des véhicules pour de la donnée (data carier), et sont donc susceptibles d'être transformées en Inline Classes quand le projet Valhalla sortira. C'est le cas par exemple de la classe Optional ou des wrappers sur les primitives.

À partir de Java 16, l'utilisation de ces classes d'une manière incompatible avec les inline classes va générer des warnings : utilisation des constructeurs (qui sont dépréciés), synchronisation, utilisation incorrecte du == ou du !=.

Performance

Beaucoup d'intrinsics ont été ajoutés à la JVM :

Temps de démarrage

Java 16 a vu son lot d'optimisations du temps de démarrage de la JVM. Claes Redestad, l'un des ingénieurs de chez Oracle qui travaille sur le sujet, a écrit un article assez intéressant qui reprend les différentes optimisations du temps de démarrage de la JVM depuis Java 8 et se pose la question des optimisations encore à faire pour Java 17 : Towards OpenJDK 17.

Release après release, le temps de démarrage de la JVM à quasiment été divisé par deux depuis Java 8 (après un accroissement non négligeable en Java 9 suite à l'introduction des modules). Sachant qu'une JVM démarre en moins de 40ms, Claes se pose la question de savoir si c'est encore nécessaire de travailler sur le sujet ?

Pour ce qui concerne Java 16, on peut noter, entre autre, de nombreuses améliorations de la fonctionnalité CDS :

  • JDK-8244778 : Archive full module graph in CDS
  • JDK-8247536 : Support for pre-generated java.lang.invoke classes in CDS static archive
  • JDK-8247666 : Support Lambda proxy classes in static CDS archive

CDS - Class Data Sharing, permet d'enregistrer dans une archive les données des metadatas des classes lors du lancement de la JVM, pour les réutiliser lors de lancements successifs, optimisant alors le temps de démarrage de cette dernière.
Vous pourrez trouver plus d'informations dans mon article QUARKUS, JLINK ET APPLICATION CLASS DATA SHARING (APPCDS).

La JVM contient une archive par défaut avec les metadatas de certaines classes du JDK, des changements sur la fonctionnalité CDS profitent donc automatiquement à tout le monde.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.