Démarrage JVM 8 vs 9

Démarrage JVM 8 vs 9

Introduction

En parcourant la mailing liste d’open JDK (core-lib-dev) j’ai vu plusieurs threads de mail à propos d’optimisation de temps de démarrage et d’occupation mémoire d’une JVM « minimale« .

Ce travail a été réalisé en grande partie par Claes Redestad (Oracle) lors du développement de Java 9.

J’ai donc décidé de tester la différence entre un HelloWorld (en version standard et avec utilisation de Lambda) entre Java 8 (update 51 pour ne pas risquer de backport des optimisations en question sur cette version) et Java 9 (ea build 176). Pour réaliser ces tests j’ai utilisé les images docker azul/zulu-openjdk:8u51 et azul/zulu-openjdk:9ea et les deux classes suivantes :

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello World");
	}
}
public class HelloWorldLambda {
	public static void main(String[] args) {
		Runnable hello = () -> System.out.println("Hello lambda world");
		hello.run();
	}
}

J’ai ensuite utilisé la commande Linux time pour connaître le temps de traitement et la fonctionnalité de Native Memory Tracking de Java pour connaître la mémoire allouée.

Java 8

time java HelloWorld
real    0m0.085s
user    0m0.078s
sys     0m0.019s
time java HelloWorldLambda
real    0m0.185s
user    0m0.236s
sys     0m0.032s
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary \
     -XX:+PrintNMTStatistics -Xmx16m -Xms16m HelloWorld
Total: reserved=1356124KB, committed=57156KB
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary \
     -XX:+PrintNMTStatistics -Xmx16m -Xms16m HelloWorldLambda
Total: reserved=1358869KB, committed=59901KB

Constatation : une JVM ça démarre vite (moins de 100ms) mais ça utilise quand même plus de 57Mo de RAM juste pour un HelloWorld! L’utilisation de Lambda implique un temps de démarrage plus que doublé et un overhead de mémoire de 2Mo.

Hum, 57Mo quand même d’utilisation mémoire pour un simple HelloWorld, je me suis souvenu d’un article fort intéressant sur le sujet écrit par Alexey Shipilev, je me suis replongé dedans pour creuser la question : https://shipilev.net/jvm-anatomy-park/12-native-memory-tracking/ et ai ajouté quelques options au démarrage de ma JVM :

  • SerialGC : le garbage collector qui démarre le plus vite et nécessite le moins de mémoire
  • Une heap à 16Mo : largement suffisant pour un HelloWorld
  • Désactivation du TieredCompilation qui lui aussi nécessite plus de mémoire
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorld
Total: reserved=1134567KB, committed=35279KB
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorldLambda
Total: reserved=1135918KB, committed=36630KB
time java -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorld
real    0m0.084s
user    0m0.077s
sys     0m0.014s
time java -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorldLambda
real    0m0.172s
user    0m0.197s
sys     0m0.018s

Constatation : Le temps de démarrage n’est pas significativement réduit dans le cas standard (mais l’est avec lambda) mais l’occupation mémoire l’est grandement. On gagne près de 20Mo grâce ces options de démarrage.

Java 9

time java HelloWorld
real    0m0.161s
user    0m0.177s
sys     0m0.048s
time java HelloWorldLambda
Hello lambda world
real    0m0.199s
user    0m0.228s
sys     0m0.042s
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary \
     -XX:+PrintNMTStatistics -Xmx16m -Xms16m HelloWorld
Total: reserved=1389612KB, committed=97536KB
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary \
     -XX:+PrintNMTStatistics -Xmx16m -Xms16m HelloWorldLambda
Total: reserved=1390008KB, committed=98188KB

Constatation : En Java 9, malgré le travail d’optimisation du démarrage de la JVM, le temps de démarrage est moins bon hors lambda mais meilleur avec lambda qu’en Java 8. Il reste donc du travail à faire, JPMS (project Jigsaw) est peut-être le fautif ici. Côté mémoire, il y a une forte augmentation.

java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorld
Total: reserved=1137041KB, committed=38117KB
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorldLambda
Total: reserved=1137178KB, committed=38510KB
time java -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorld
real    0m0.152s
user    0m0.143s
sys     0m0.022s
time java -Xmx16m -Xms16m -XX:+UseSerialGC -XX:-TieredCompilation HelloWorldLambda
real    0m0.202s
user    0m0.188s
sys     0m0.020s

Constatation : En optimisant les options JVM, le temps de démarrage n’évolue pas beaucoup mais l’utilisation mémoire baisse drastiquement et retourne quasiment au même niveau qu’en Java 8 dans la même configuration. Sachant que Java 9 vient avec Le garbage collector G1 par défaut (au lieu du Parallel GC), la consommation excessive doit provenir de celui-ci.

Essayons donc de vérifier en activant le ParallelGC qui était le défaut en Java 8.

java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseParallelOldGC -XX:-TieredCompilation HelloWorld
Total: reserved=1157722KB, committed=58798KB

java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics \
     -Xmx16m -Xms16m -XX:+UseParallelOldGC -XX:-TieredCompilation HelloWorldLambda
Total: reserved=1157860KB, committed=59192KB

Constatation : C’est donc bien l’utilisation de G1 par défaut dans Java 9 qui est la cause de l’augmentation de l’utilisation mémoire plus importante en 9 qu’en 8.

Conclusion

Première constatation : L’idée préconçue que java est lent à démarrer et ne peux être utilisé pour des applications de type ligne de commande est erronée! Une JVM démarre en moins de 100ms!

Deuxième constatation : Malgré tout le travail fait par les ingénieurs d’Oracle Java 9 démarre plus lentement que Java 8 et utilise plus de mémoire. Certain choix entre en contradiction directe avec l’optimisation du démarrage d’une JVM (entre autre, le passage de G1 comme garbage collector par défaut).

Troisième constatation : L’écart entre code utilisant et n’utilisant pas les lambda s’est énormément réduit entre Java 8 et Java 9

Attention : le protocole de test utilisé ici n’était peut-être pas très rigoureux, lancer plusieurs fois un process via la commande time n’est pas la panacée. De plus le développent de Java 9 n’est pas encore terminé, on peut espérer que la version finale comblera en partie ou totalement l’écart de temps de démarrage entre une JVM 8 et 9. A tester donc avec une version finale.

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.