Comment réchauffer la JVM

1. Vue d'ensemble

La JVM est l'une des machines virtuelles les plus anciennes et les plus puissantes jamais créées.

Dans cet article, nous examinons rapidement ce que signifie réchauffer une JVM et comment le faire.

2. Principes de base de l'architecture JVM

Chaque fois qu'un nouveau processus JVM démarre, toutes les classes requises sont chargées en mémoire par une instance du ClassLoader. Ce processus se déroule en trois étapes:

  1. Chargement de la classe Bootstrap: Le « Bootstrap Class Loader » charge le code Java et les classes Java essentielles telles que java.lang.Object en mémoire. Ces classes chargées résident dans JRE \ lib \ rt.jar .
  2. Chargement de la classe d'extension : ExtClassLoader est responsable du chargement de tous les fichiers JAR situés dans le chemin java.ext.dirs . Dans les applications non-Maven ou non-Gradle, où un développeur ajoute manuellement des JAR, toutes ces classes sont chargées pendant cette phase.
  3. Chargement de la classe d'application : l'AppClassLoader charge toutes les classes situées dans le chemin de la classe d'application.

Ce processus d'initialisation est basé sur un schéma de chargement paresseux.

3. Qu'est-ce que le préchauffage de la JVM

Une fois le chargement de classe terminé, toutes les classes importantes (utilisées au moment du démarrage du processus) sont poussées dans le cache JVM (code natif) - ce qui les rend accessibles plus rapidement pendant l'exécution. Les autres classes sont chargées sur une base par demande.

La première requête adressée à une application Web Java est souvent nettement plus lente que le temps de réponse moyen pendant la durée de vie du processus. Cette période de préchauffage peut généralement être attribuée au chargement de classe paresseux et à la compilation juste à temps.

En gardant cela à l'esprit, pour les applications à faible latence, nous devons mettre en cache toutes les classes au préalable - afin qu'elles soient disponibles instantanément lors de l'accès à l'exécution.

Ce processus de réglage de la JVM est appelé préchauffage.

4. Compilation à plusieurs niveaux

Grâce à l'architecture sonore de la JVM, les méthodes fréquemment utilisées sont chargées dans le cache natif pendant le cycle de vie de l'application.

Nous pouvons utiliser cette propriété pour forcer le chargement de méthodes critiques dans le cache au démarrage d'une application. Dans cette mesure, nous devons définir un argument VM nommé Tiered Compilation :

-XX:CompileThreshold -XX:TieredCompilation

Normalement, la machine virtuelle utilise l'interpréteur pour collecter des informations de profilage sur les méthodes qui sont introduites dans le compilateur. Dans le schéma à plusieurs niveaux, en plus de l'interpréteur, le compilateur client est utilisé pour générer des versions compilées de méthodes qui collectent des informations de profilage les concernant.

Le code compilé étant nettement plus rapide que le code interprété, le programme s'exécute avec de meilleures performances pendant la phase de profilage.

Les applications exécutées sur JBoss et JDK version 7 avec cet argument VM activé ont tendance à planter après un certain temps en raison d'un bogue documenté. Le problème a été résolu dans la version 8 de JDK.

Un autre point à noter ici est que pour forcer le chargement, nous devons nous assurer que toutes (ou la plupart) des classes qui vont être exécutées doivent être accédées. C'est similaire à la détermination de la couverture du code lors des tests unitaires. Plus le code est couvert, meilleures seront les performances.

La section suivante montre comment cela peut être mis en œuvre.

5. Mise en œuvre manuelle

Nous pouvons implémenter une technique alternative pour réchauffer la JVM. Dans ce cas, un simple échauffement manuel peut inclure la répétition de la création de différentes classes des milliers de fois dès le démarrage de l'application.

Tout d'abord, nous devons créer une classe factice avec une méthode normale:

public class Dummy { public void m() { } }

Ensuite, nous devons créer une classe qui a une méthode statique qui sera exécutée au moins 100000 fois dès que l'application démarre et à chaque exécution, elle crée une nouvelle instance de la classe factice susmentionnée que nous avons créée précédemment:

public class ManualClassLoader { protected static void load() { for (int i = 0; i < 100000; i++) { Dummy dummy = new Dummy(); dummy.m(); } } }

Maintenant, afin de mesurer le gain de performance , nous devons créer une classe principale. Cette classe contient un bloc statique qui contient un appel direct à la méthode load () de ManualClassLoader .

Dans la fonction principale, nous appelons à nouveau la méthode load () du ManualClassLoader et capturons l'heure du système en nanosecondes juste avant et après notre appel de fonction. Enfin, nous soustrayons ces temps pour obtenir le temps d'exécution réel.

Nous devons exécuter l'application deux fois; une fois avec l' appel de méthode load () à l'intérieur du bloc statique et une fois sans cet appel de méthode:

public class MainApplication { static { long start = System.nanoTime(); ManualClassLoader.load(); long end = System.nanoTime(); System.out.println("Warm Up time : " + (end - start)); } public static void main(String[] args) { long start = System.nanoTime(); ManualClassLoader.load(); long end = System.nanoTime(); System.out.println("Total time taken : " + (end - start)); } }

Ci-dessous les résultats sont reproduits en nanosecondes:

Avec Warm Up Pas d'échauffement Différence(%)
1220056 8903640 730
1083797 13609530 1256
1026025 9283837 905
1024047 7234871 706
868782 9146180 1053

Comme prévu, avec l'approche d'échauffement montre des performances bien meilleures que la normale.

Bien sûr, il s'agit d'un point de référence très simpliste et ne fournit qu'un aperçu de la surface de l'impact de cette technique. De plus, il est important de comprendre qu'avec une application réelle, nous devons nous familiariser avec les chemins de code typiques du système.

6. Outils

We can also use several tools to warm up the JVM. One of the most well-known tools is the Java Microbenchmark Harness, JMH. It's generally used for micro-benchmarking. Once it is loaded, it repeatedly hits a code snippet and monitors the warm-up iteration cycle.

To use it we need to add another dependency to the pom.xml:

 org.openjdk.jmh jmh-core 1.19   org.openjdk.jmh jmh-generator-annprocess 1.19 

We can check the latest version of JMH in Central Maven Repository.

Alternatively, we can use JMH's maven plugin to generate a sample project:

mvn archetype:generate \ -DinteractiveMode=false \ -DarchetypeGroupId=org.openjdk.jmh \ -DarchetypeArtifactId=jmh-java-benchmark-archetype \ -DgroupId=com.baeldung \ -DartifactId=test \ -Dversion=1.0

Next, let's create a main method:

public static void main(String[] args) throws RunnerException, IOException { Main.main(args); }

Now, we need to create a method and annotate it with JMH's @Benchmark annotation:

@Benchmark public void init() { //code snippet }

Inside this init method, we need to write code that needs to be executed repeatedly in order to warm up.

7. Performance Benchmark

In the last 20 years, most contributions to Java were related to the GC (Garbage Collector) and JIT (Just In Time Compiler). Almost all of the performance benchmarks found online are done on a JVM already running for some time. However,

However, Beihang University has published a benchmark report taking into account JVM warm-up time. They used Hadoop and Spark based systems to process massive data:

Here HotTub designates the environment in which the JVM was warmed up.

As you can see, the speed-up can be significant, especially for relatively small read operations – which is why this data is interesting to consider.

8. Conclusion

In this quick article, we showed how the JVM loads classes when an application starts and how we can warm up the JVM in order gain a performance boost.

Ce livre contient plus d'informations et de directives sur le sujet si vous souhaitez continuer.

Et, comme toujours, le code source complet est disponible sur GitHub.