Software Craftmanship

GraalVM MasterClass

Découvrez nos jobs
Vous ambitionnez de devenir Tech Lead ou de faire du conseil de haut-niveau ? Nous avons des challenges à votre hauteur !

Il y a 25 ans, la société Sun créait le langage Java. La jvm n’a cessé de gagner en performance et s’enrichir en fonctionnalités. Une grande partie du succès du Java réside sur sa portabilité et sa facilité de gestion de la mémoire avec son garbage collector. Cependant, nous oublions souvent de citer son système de compilation à chaud, le Just In Time.

Mais c’est quoi la compilation à chaud ?

Il s’agit d’une technique visant à améliorer la performance de bytecode-compilés par une traduction en code machine natif au moment de l’exécution. La compilation à la volée se fonde sur deux anciennes idées : la compilation de bytecode et la compilation dynamique.

Dans un système dit bytecode-compilé, le code source est compilé à l’avance ou à la volée (lors de l’exécution) dans une représentation intermédiaire, le bytecode. C’est le cas par exemple des langages Limbo, Smalltalk, Perl, PHP, Python, Ruby, Lua, GNU Common Lisp ou encore Java, entre autres.

[baptiste@KEYWER GraalArticle]$ vi Greeting.java
[baptiste@KEYWER GraalArticle]$ javac Greeting.java 
[baptiste@KEYWER GraalArticle]$ ls -als
total 16
4 drwxr-xr-x. 2 baptiste baptiste 4096 14 mai   10:54 .
4 drwxr-xr-x. 3 baptiste baptiste 4096 14 mai   10:50 ..
4 -rw-rw-r--. 1 baptiste baptiste  461 14 mai   10:54 Greeting.class <= ByteCode
4 -rw-rw-r--. 1 baptiste baptiste  125 14 mai   10:53 Greeting.java
[baptiste@KEYWER GraalArticle]$ java Greeting 
Hello !

Le bytecode n’est pas un code machine, c’est-à-dire que ce n’est pas un code optimisé pour un type d’architecture d’ordinateur en particulier. On dit du bytecode qu’il est portable entre différentes architectures. Ce bytecode est ensuite interprété ou bien exécuté par une machine virtuelle.

La production de bytecode n’est que la première étape d’un processus d’exécution plus complexe. Le bytecode est ensuite déployé sur le système cible, lors de son execution le JIT, le traduit en code machine natif (ie. optimisé pour l’architecture de la machine exécutant le programme). Ceci peut être fait sur un fichier entier, ou spécifiquement sur une fonction du programme.

La compilation à la volée s’adapte dynamiquement à la charge de travail courante du logiciel, en compilant le code « chaud », c’est-à-dire le code le plus utilisé à un moment donné. Obtenir du code machine optimisé se fait beaucoup plus rapidement depuis du bytecode que depuis du code source. Comme le bytecode déployé est portable, la compilation à la volée est envisageable pour tout type d’architecture, à la condition d’avoir un compilateur JIT pour cette architecture.

LES SOURCES DE L’OPENJDK SONT CONSULTABLES SUR GITHUB.

Vous remarquerez qu’il est composé de sources de plusieurs langages.

EN PARCOURANT LES SOURCES, NOUS TROUVONS DU CODE C++ SPÉCIFIQUE À DE NOMBREUSES ARCHITECTURES :

[baptiste@KEYWER hotspot]$ tree . -d
.
├── cpu
│   ├── aarch64
│   ├── arm
│   ├── ppc
│   ├── s390
│   ├── sparc
│   ├── x86
│   └── zero
├── os
│   ├── aix
│   ├── bsd
│   ├── linux
│   ├── posix
│   ├── solaris
│   └── windows
└── os_cpu
    ├── aix_ppc
    ├── bsd_x86
    ├── bsd_zero
    ├── linux_aarch64
    ├── linux_arm
    ├── linux_ppc
    ├── linux_s390
    ├── linux_sparc
    ├── linux_x86
    ├── linux_zero
    ├── solaris_sparc
    ├── solaris_x86
    └── windows_x86

Le JIT est composé de deux compilateurs :

LA JVM COMBINE L’USAGE DE CES DEUX COMPILATEURS.

Lors de la première exécution du code, la Jvm utilisera le C1. La compilation C2 se déclenche selon une table de statistiques maintenue par la Jvm, c’est elle qui décidera de son lancement.

L’usage de ce mécanisme a permis de faire des gains énormes en performance, mais C2 est devenu de plus en plus complexe et difficile à maintenir. C’est en ayant conscience des limitations de cette technique que la société Oracle publia dans sa version 9 de Java une API permettant de fournir sa propre implémentation de compilation de code (JEP-243). C’est là qu’intervient GraalVM !


GraalVM est une machine virtuelle développée par Oracle. Bien que basée sur la HotSpot nous allons voir en quoi cette JVM en est différente.

Mise en place :

GraalVm est divisée en deux éditions la Community et l’Entreprise Edition. Pour un usage personnel il est tout à fait possible de télécharger la version Entreprise. Deux versions de HotSpot sont proposées la 8 et la 11.

[baptiste@KEYWER latest]$ $GRAALVM_HOME/bin/java -version
java version "11.0.7" 2020-04-14 LTS
Java(TM) SE Runtime Environment GraalVM EE 20.0.1 (build 11.0.7+8-LTS-jvmci-20.0-b04)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 20.0.1 (build 11.0.7+8-LTS-jvmci-20.0-b04, mixed mode, sharing)

UNE NOUVELLE IMPLÉMENTATION DU JIT

Son implémentation a totalement été revue, le C++ a été remplacé par du Java, plus facile à maintenir.

Pour le tester nous suivrons un exemple proposé sur le site de GraalVM (n’hésitez pas à y faire un tour).

Voici un programme permettant de compter le nombre de caractères en majuscule dans un texte, exécuté de nombreuses fois.

public class CountUppercase {
    static final int ITERATIONS = Math.max(Integer.getInteger("iterations", 1), 1);
    public static void main(String[] args) {
        String sentence = String.join(" ", args);
        for (int iter = 0; iter < ITERATIONS; iter++) {
            if (ITERATIONS != 1) System.out.println("-- iteration " + (iter + 1) + " --");
            long total = 0, start = System.currentTimeMillis(), last = start;
            for (int i = 1; i < 10_000_000; i++) {
                total += sentence.chars().filter(Character::isUpperCase).count();
                if (i % 1_000_000 == 0) {
                    long now = System.currentTimeMillis();
                    System.out.printf("%d (%d ms)%n", i / 1_000_000, now - last);
                    last = now;
                }
            }
            System.out.printf("total: %d (%d ms)%n", total, System.currentTimeMillis() - start);
        }
    }
}

Nous allons exécuter ce code en désactivant la nouvelle version du JIT avec l’argument -XX:-UseJVMCICompiler

[baptiste@KEYWER tuto_graalvm]$ $GRAALVM_HOME/bin/javac CountUppercase.java
[baptiste@KEYWER tuto_graalvm]$ $GRAALVM_HOME/bin/java -XX:-UseJVMCICompiler CountUppercase In 2020 I would like to run ALL languages in one VM.
1 (497 ms)
2 (433 ms)
3 (415 ms)
4 (410 ms)
5 (415 ms)
6 (414 ms)
7 (415 ms)
8 (414 ms)
9 (413 ms)
total: 69999993 (4239 ms)

Le traitement a pris 4239 ms.

En aparté, le – désactive (-XX:-UseJVMCICompiler) et le + active (-XX:+UseJVMCICompiler).

Nous allons relancer la même exécution en laissant le JVMCICompiler activé par défaut.

[baptiste@KEYWER tuto_graalvm]$ $GRAALVM_HOME/bin/java CountUppercase In 2020 I would like to run ALL languages in one VM.
1 (152 ms)
2 (152 ms)
3 (79 ms)
4 (77 ms)
5 (79 ms)
6 (78 ms)
7 (77 ms)
8 (80 ms)
9 (79 ms)
total: 69999993 (932 ms)

Cette fois-ci l’exécution a pris 920 ms, soit 4 fois moins de temps !

LA COMPILATION NATIVE

Aussi appelé AOT (Ahead Of Time), cette fonctionnalité a été introduite dans Java 9 via la JEP 295. L’idée est de considérer que tout ce qui est fait par le JIT en runtime peut être réalisé en phase de compilation.

Prenons une classe dont une fonction est appelée plusieurs fois d’affilé :

public class MultipleCall {

  public int f() throws Exception {
    int a = 5;
    return a;
  }

  public static void main(String[] args) throws Exception {
    for (int i = 1; i <= 10; i++) {
      System.out.println("call " + Integer.valueOf(i));
      long a = System.nanoTime();
      new Test().f();
      long b = System.nanoTime();
      System.out.println("elapsed= " + (b-a));
    }

  }
}

Commençons par expérimenter ce programme via une HotSpot :

[baptiste@KEYWER tuto_graalvm]$ $JAVA_HOME/bin/java -version
openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)
[baptiste@KEYWER native-list-dir]$ $JAVA_HOME/bin/javac MultipleCall.java 
[baptiste@KEYWER native-list-dir]$ time $JAVA_HOME/bin/java MultipleCall 
[baptiste@DESKTOP-FUI7H3K 02_AOT_HOTSPOT]$ $JAVA_HOME/bin/java Test
call 1
elapsed= 3740
call 2
elapsed= 709
call 3
elapsed= 352
call 4
elapsed= 317
call 5
elapsed= 349
call 6
elapsed= 306
call 7
elapsed= 281
call 8
elapsed= 297
call 9
elapsed= 295
call 10
elapsed= 322

Pour utiliser l’AOT il nous faut utiliser l’exécutable jaotc

[baptiste@KEYWER native-list-dir]$ $JAVA_HOME/bin/jaotc --output MultipleCall.so MultipleCall

Cette commande va convertir notre fichier Java en une librairie dans un format appelé SharedObjects (.so).

[baptiste@KEYWER tuto_graalvm]$ ls -alhs
total 8,1M
4,0K drwxr-xr-x.  3 baptiste baptiste 4,0K 14 mai   17:58  .
4,0K drwxr-xr-x.  9 baptiste baptiste 4,0K 14 mai   10:49  ..
4,0K -rw-rw-r--.  1 baptiste baptiste 2,4K 14 mai   17:57  MultipleCall.class
4,0K -rw-rw-r--.  1 baptiste baptiste  929  2 mai   11:44  MultipleCall.java
144K -rw-rw-r--.  1 baptiste baptiste 141K 14 mai   17:58  MultipleCall.so

Nous l’executerons de la manière suivante:

[baptiste@KEYWER tuto_graalvm]$ $JAVA_HOME/bin/java -XX:AOTLibrary=./MultipleCall.so MultipleCall 
call 1
elapsed= 2227
call 2
elapsed= 945
call 3
elapsed= 368
call 4
elapsed= 287
call 5
elapsed= 296
call 6
elapsed= 227
call 7
elapsed= 263
call 8
elapsed= 263
call 9
elapsed= 296
call 10
elapsed= 298

On constate de meilleurs performances au démarrage, les résultats finissent ensuite par tendre vers les mêmes résultats. Il est faux de se dire que l’AOT permet d’avoir un programme bien plus performant, en revanche il permet d’accéder plus rapidement à un code optimal, là ou le JIT aurait nécessité plusieurs itérations.  A noter qu’il est également possible d’effectuer cette manipulation sur un module notamment ceux du JDK.

Expérimentons cette fonctionnalité avec GraalVM

Prenons le code suivant trouvé sur le site officiel de GraalVM:

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class ListDir {
        public static void main(String[] args) throws java.io.IOException {

                String root = ".";
                if(args.length > 0) {
                        root = args[0];
                }
                System.out.println("Walking path: " + Paths.get(root));

                long[] size = {0};
                long[] count = {0};

                try (Stream<Path> paths = Files.walk(Paths.get(root))) {
                        paths.filter(Files::isRegularFile).forEach((Path p) -> {
                                File f = p.toFile();
                                size[0] += f.length();
                                count[0] += 1;
                        });
                }

                System.out.println("Total: " + count[0] + " files, total size = " + size[0] + " bytes");
        }
}

Voici le résultat d’une éxecution classique:

[baptiste@KEYWER native-list-dir]$ $GRAALVM_HOME/bin/javac ListDir.java
[baptiste@KEYWER native-list-dir]$ time $GRAALVM_HOME/bin/java ListDir ..
Walking path: ..
Total: 611 files, total size = 55309806 bytes

real 0m0,197s
user 0m0,242s
sys 0m0,041s

L’exécutable de compilation native n’est pas présent par défaut. Pour l’édition entreprise il va falloir le télécharger.

[baptiste@KEYWER ~]$ cd $GRAALVM_HOME/bin
[baptiste@KEYWER bin]$ ./gu -L install native-image-installable-svm-svmee-java11-linux-amd64-20.0.1.jar 
Processing Component archive: native-image-installable-svm-svmee-java11-linux-amd64-20.0.1.jar
Installing new component: Native Image (org.graalvm.native-image, version 20.0.1)
[baptiste@KEYWER bin]$ ls -alhs
0 lrwxrwxrwx.  1 baptiste baptiste        27 14 mai   19:09 native-image -> ../lib/svm/bin/native-image

L’exécutable gu ou Graal Updater vous permet d’installer des composants en ligne de commande.

[baptiste@KEYWER native-list-dir]$ $GRAALVM_HOME/bin/javac ListDir.java
[baptiste@KEYWER native-list-dir]$ $GRAALVM_HOME/bin/native-image ListDir
Build on Server(pid: 20294, port: 43927)
[listdir:20294]    classlist:     119.83 ms,  1.00 GB
[listdir:20294]        (cap):     743.81 ms,  1.00 GB
[listdir:20294]        setup:   1,945.27 ms,  1.00 GB
[listdir:20294]   (typeflow):   5,436.31 ms,  1.20 GB
[listdir:20294]    (objects):   3,881.34 ms,  1.20 GB
[listdir:20294]   (features):     227.98 ms,  1.20 GB
[listdir:20294]     analysis:   9,894.44 ms,  1.20 GB
[listdir:20294]     (clinit):     189.02 ms,  1.20 GB
[listdir:20294]     universe:     490.97 ms,  1.20 GB
[listdir:20294]      (parse):   1,228.73 ms,  1.20 GB
[listdir:20294]     (inline):   1,114.86 ms,  1.20 GB
[listdir:20294]    (compile):   8,336.15 ms,  1.50 GB
[listdir:20294]      compile:  11,062.47 ms,  1.50 GB
[listdir:20294]        image:     686.83 ms,  1.50 GB
[listdir:20294]        write:     137.18 ms,  1.50 GB
[listdir:20294]      [total]:  24,546.82 ms,  1.50 GB

La première chose que l’on constate est que le temps de création de l’image est très long ! 25 secondes pour compiler une classe.

Mais que fait GraalVM pendant tout ce temps ?

GraalVM va effectuer tout un tas d’optimisations, notamment établir un graph du code appelé, pour ensuite enlever le code inutile.

[baptiste@KEYWER native-list-dir]$ ls -alsh
total 6,8M
4,0K drwxrwxr-x.  2 baptiste baptiste 4,0K 15 mai   10:46 .
4,0K drwxrwxr-x. 16 baptiste baptiste 4,0K  2 mai   12:15 ..
6,7M -rwxrwxr-x.  1 baptiste baptiste 6,7M 15 mai   10:46 listdir

On constate que l’exécutable pèse seulement 7M ! Pour le lancer, inutile de posséder un JRE puisqu’il s’agit de code natif.

IMAGINEZ LE GAIN POUR UNE IMAGE DOCKER !!!

[baptiste@KEYWER native-list-dir]$ time ./listdir ..
Walking path: ..
Total: 611 files, total size = 55309806 bytes

real 0m0,034s
user 0m0,014s
sys 0m0,020s

On constate un gain supplémentaire par rapport à l’AOT de la HotSpot. A noter que les applications natives s’exécutent sur machine virtuelle appelée SubstrateVM.

Ce n’est pas terminé, Graal propose une fonctionnalité (dans sa version entreprise), permettant de créer un fichier de profiling lors de l’exécution. Ce fichier peut être utilisé pour générer une image encore plus performante.

[baptiste@KEYWER native-list-dir]$ $GRAALVMEE_HOME/bin/native-image --pgo-instrument ListDir
...
[baptiste@KEYWER native-list-dir]$ time ./listdir ..
Walking path: ..
Total: 611 files, total size = 55309806 bytes

real 0m0,034s
user 0m0,014s
sys 0m0,020s

[baptiste@KEYWER native-list-dir]$ ls -als
total 46188
    4 drwxrwxr-x.  2 baptiste baptiste     4096 15 mai   11:19 .
    4 drwxrwxr-x. 16 baptiste baptiste     4096  2 mai   12:15 ..
 1840 -rw-------.  1 baptiste baptiste  1880350 15 mai   11:19 default.iprof
 
[baptiste@KEYWER native-list-dir]$ $GRAALVMEE_HOME/bin/native-image --pgo ListDir
[baptiste@KEYWER native-list-dir]$ time ./listdir ..
Walking path: ..
Total: 611 files, total size = 55309806 bytes

real 0m0,018s
user 0m0,006s
sys 0m0,011s
HotSpot 11GraalVM EEGraalVM EE Native ImageGraalVM EE Native Image + pgo
real 0m0,197suser 0m0,242ssys 0m0,041sreal 0m0,161suser 0m0,277ssys 0m0,037sreal 0m0,034suser 0m0,014ssys 0m0,020sreal 0m0,018suser 0m0,006ssys 0m0,011s

Résultat des courses, la taille de l’exécutable pèse seulement 7 Mo, plutôt que 200 Mo de JRE + 5 Mo de Jar. Les performances sont bien meilleures, plus de 10 fois plus rapide. Parfait pour un contexte cloud !

MAIS TOUT ÇA N’EST-T-IL PAS TROP BEAU ?

Eh oui, je ne vous ai pas tout dit. Ces avantages ont un prix, certaines fonctionnalités Java ne sont pas disponibles pour les images natives ! Je vous ai expliqué plus haut que lors de la création de l’image, Graal effectuait un graph permettant de retirer le code inutile et cela lors de la phase de compilation.

MAIS QU’EN EST-IL DES INSTRUCTIONS EXÉCUTÉES EN RUNTIME TELLES QUE LA RÉFLEXION ? COMMENT LES INITIALISATIONS STATIQUES SONT-ELLES RÉALISÉES ?

Commençons par nous intéresser à la réflexion

LA CONFIGURATION PAR FICHIER

Il suffit de lister les éléments pouvant être la cible de réflexion via un argument au moment de la génération de l’image.

$GRAALVM_HOME/bin/native-image -H:ReflectionConfigurationFiles=/path/to/reflectconfig -jar build/libs/aot-reflection-test.jar 

Voici à quoi ressemble le fichier, la déclaration peut être fine et aller jusqu’au niveau d’un attribut ou méthode d’une classe.

[
  {
    "name" : "java.lang.Class",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredClasses" : true,
    "allPublicClasses" : true
  },
  {
    "name" : "java.lang.String",
    "fields" : [
      { "name" : "value", "allowWrite" : true },
      { "name" : "hash" }
    ],
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] },
      { "name" : "<init>", "parameterTypes" : ["char[]"] },
      { "name" : "charAt" },
      { "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
    ]
  },
  {
    "name" : "java.lang.String$CaseInsensitiveComparator",
    "methods" : [
      { "name" : "compare" }
    ]
  }
]

Je vous joins les sources d’un projet expérimentant certains comportements de réflexion avec Graal.

UTILISER LES FEATURES GRAAL :

@AutomaticFeature
class RuntimeReflectionRegistrationFeature implements Feature {
  public void beforeAnalysis(BeforeAnalysisAccess access) {
    try {
      RuntimeReflection.register(String.class);
      RuntimeReflection.register(/* finalIsWritable: */ true, String.class.getDeclaredField("value"));
      RuntimeReflection.register(String.class.getDeclaredField("hash"));
      RuntimeReflection.register(String.class.getDeclaredConstructor(char[].class));
      RuntimeReflection.register(String.class.getDeclaredMethod("charAt", int.class));
      RuntimeReflection.register(String.class.getDeclaredMethod("format", String.class, Object[].class));
      RuntimeReflection.register(String.CaseInsensitiveComparator.class);
      RuntimeReflection.register(String.CaseInsensitiveComparator.class.getDeclaredMethod("compare", String.class, String.class));
    } catch (NoSuchMethodException | NoSuchFieldException e) { ... }
  }
}

Graal propose une API Feature permettant de configurer programmatiquement son image en buildTime.

SOLUTION 1:

Utiliser l’annotation @AutomaticFeature

SOLUTION 2:

L’activer via un argument lors de la génération de l’image : –features=<fqcn>

SOLUTION 3:

L’activer via un fichier de configuration dans le répertoire Resources/META-INF/GROUP_ID/ARTIFACT_ID/native-image.properties

Args = --features=com.keywer.graalvm.reflection.ConfigureAtBuildTimeFeature

Les initialisations de code static :

Par défaut elle sont dorénavant réalisées en phase de runtime. Pour gagner en temps de lancement il est possible d’effectuer ces initialisations lors de la génération de l’image.

Il vous faudra jouer avec ces deux arguments pour distinguer les classes a initialiser en :

Pour les applications complexes, cette opération peut se révéler être un véritable casse-tête.

Vous aurez alors la possibilité de tracer les dépendances via l’argument : -H:+TraceClassInitialization

Et ce n’est pas tout !

[baptiste@KEYWER bin]$ ls
bundle       javac      jhsdb       js            node            rmic
bundler      javadoc    jimage      jshell        npm             rmid
gem          javap      jinfo       jstack        pack200         rmiregistry
graalpython  jcmd       jjs         jstat         polyglot        Rscript
gu           jconsole   jlink       jstatd        R               ruby
irb          jdb        jmap        jvisualvm     rake            serialver
jar          jdeprscan  jmod        keytool       rdoc            truffleruby
jarsigner    jdeps      jps         lli           rebuild-images  unpack200
java         jfr        jrunscript  native-image  ri

Et oui vous ne rêvez pas, c’est bien l’exécutable Node que vous voyez. Je ne m’étendrai pas sur le sujet mais Graal est aussi une JVM dite polyglote. Elle est capable d’exécuter du code de plusieurs langages ! Oracle fournit une API appelée Truffle permettant à un langage d’exploiter le système de gestion de la mémoire de sa machine virtuelle.

Les outils

Est-il possible de faire du remote débug d’une application native ?

Pour les applications node JS oui, via devTool de Chrome.

$ node --inspect --jvm HelloWorld.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/js_app.html?ws=127.0.1.1:9229/76fcb6dd-35267eb09c3
Server running at http://localhost:8000/

Peut-on utiliser VisualVM avec une application native ?

Oui pour la version Entreprise, en ajoutant l’argument -H:+AllowVMInspection lors de la génération de l’image. Attention vous n’aurez pas accès aux Threads ou aux Beans JMX.

Existe-t-il un plugin maven ?

<plugin>
    <groupId>com.oracle.substratevm</groupId>
    <artifactId>native-image-maven-plugin</artifactId>
    <version>${graalvm.plugin.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>native-image</goal>
            </goals>
            <phase>package</phase>
        </execution>
    </executions>
</plugin>

Quels sont les frameworks Java compatibles avec GraalVM ?

Un gros travail est actuellement mené sur Spring pour le rendre totalement compatible.

Pour conclure

Oracle frappe fort et crée une petite révolution en proposant 3 évolutions majeures.

GraalVM a longtemps été considérée comme une VM expérimentale, ce n’est clairement plus le cas. Les grands frameworks ne s’y sont pas trompés et se rendent petit à petit compatible. RedHat a largement participé à la popularisation de GraalVM via Quarkus.

De manière globale, Oracle a adopté ces derniers temps une démarche beaucoup plus conquérante en terme de développement de son langage. Que ce soit par ses travaux sur Graal ou la publication de release tous les 6 mois. Dans un monde ou des langages performants émergent régulièrement tels que Rust ou Go, il est clair qu’il s’agit d’un enjeu stratégique pour Oracle de rester leader dans ce domaine.

Sources:

https://www.graalvm.org
https://developer.okta.com/blog/2019/11/27/graalvm-java-binaries
https://rieckpil.de/whatis-graalvm/
https://fr.wikipedia.org/wiki/Compilation_%C3%A0_la_vol%C3%A9e
https://www.baeldung.com/graal-java-jit-compiler
https://www.infoq.com/fr/articles/Graal-Java-JIT-Compiler/
https://www.baeldung.com/ahead-of-time-compilation
http://macias.info/entry/202001051700_graal_reflection.md
https://vertx.io/
https://helidon.io/#/
https://spark.apache.org/
https://quarkus.io/
https://micronaut.io/
https://medium.com/graalvm/simplifying-native-image-generation-with-maven-plugin-and-embeddable-configuration-d5b283b92f57
0