Vous reprenez un vieux script de démarrage, vous passez à Java 25, et la JVM ne démarre plus :
Unrecognized VM option 'PrintGCDateStamps'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Beaucoup d’options mémoire et GC qu’on trouve encore sur le web ont changé ou disparu. Certaines sont juste ignorées, d’autres empêchent la JVM de démarrer. Et les options utiles aujourd’hui tiennent en quelques lignes.
Les options qui comptent
| Option | Rôle | Quand la changer | Quand ne pas y toucher |
|---|---|---|---|
-Xms, -Xmx | Taille initiale et maximale du heap | Presque toujours sur un serveur : la valeur par défaut est souvent trop petite | — |
-Xss | Taille de la pile de chaque thread | Après un StackOverflowError sur une récursion légitime | Sinon : la valeur par défaut suffit |
-XX:+UseG1GC, -XX:+UseParallelGC, -XX:+UseZGC | Choix du GC | Quand un besoin précis le demande (latence, débit) | Par défaut : laissez d’abord la JVM choisir |
-XX:MaxGCPauseMillis | Objectif de pause (G1 : 200 ms par défaut) | Si les pauses de G1 sont trop longues pour vous | Pour « accélérer » le GC : des pauses plus courtes peuvent coûter du débit |
-Xlog:gc* | Logs du GC | Toujours utile en production, vers un fichier | — |
-XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath | Dump du heap en cas de heap plein | En production, pour comprendre un OutOfMemoryError | — |
-XX:MaxRAMPercentage | Heap maximum en pourcentage de la mémoire (25 % par défaut) | Dans un conteneur, à la place d’un -Xmx fixe | — |
La taille du heap : -Xms et -Xmx
-Xms donne la taille initiale du heap, -Xmx sa taille maximale. Sans
ces options, la JVM prend 1/64 de la mémoire physique au départ et 1/4
au maximum. Sur ma machine (32 Go), cela donne 512 Mo et 8 Go.
Sur un serveur, on donne souvent la même valeur aux deux. Le guide d’Oracle explique pourquoi : c’est plus prévisible, car la JVM n’a plus à décider de la taille du heap. Mais en contrepartie, si la valeur est mauvaise, la JVM ne peut plus la corriger.
-Xss fixe la taille de la pile de chaque thread : 1 Mo par défaut sur
Linux et macOS en x64, 2 Mo sur Aarch64 (les Mac Apple Silicon, par
exemple).
Pauses, logs et heap dump
-XX:MaxGCPauseMillis est un objectif, pas une promesse. La
documentation parle d’un « soft goal » : la JVM fait de son mieux, mais
elle peut ne pas l’atteindre. Pour tenir un objectif plus court, elle
peut lancer le GC plus souvent, et l’application fait alors moins de
travail utile.
-Xlog:gc* écrit les logs du GC. Pour les envoyer dans un fichier :
-Xlog:gc*:file=gc.log. La rotation est déjà intégrée : par défaut, 5
fichiers de 20 Mo environ, à changer avec filecount et filesize.
-XX:+HeapDumpOnOutOfMemoryError écrit un fichier .hprof quand le
heap est plein. Elle est désactivée par défaut. Sans -XX:HeapDumpPath,
le fichier va dans le dossier courant, avec le nom java_pid<pid>.hprof.
Attention : elle ne marche que pour un heap plein, pas pour les autres
OutOfMemoryError (par exemple quand la JVM ne peut plus créer de
thread).
En conteneur
Sous Linux, la JVM détecte le conteneur toute seule : elle voit sa
limite de mémoire et son nombre de CPU. -XX:MaxRAMPercentage donne la
part de cette mémoire réservée au heap maximum, 25 % par défaut. Ce
pourcentage suit la limite du conteneur, alors qu’un -Xmx fixe ne
bouge pas quand la limite change. Le détail, avec des mesures :
Java dans Docker et Kubernetes.
Ce qui a changé : les options qui ne marchent plus
| Option | Java 8 | Java 11 | Java 17, 21 et 25 | À la place |
|---|---|---|---|---|
-XX:PermSize, -XX:MaxPermSize | Ignorée, avec un avertissement | Ignorée, avec un avertissement | Refusée | -XX:MetaspaceSize, -XX:MaxMetaspaceSize |
-XX:+UseConcMarkSweepGC | Acceptée | Acceptée, avec un avertissement (dépréciée) | Refusée | Un autre GC (CMS est supprimé depuis Java 14) |
-XX:+UseParNewGC | Acceptée | Refusée | Refusée (supprimée en Java 10) | — |
-XX:+UseCGroupMemoryLimitForHeap | Expérimentale | Refusée | Refusée (supprimée en Java 11) | Rien : la JVM détecte le conteneur |
-XX:+PrintGCDateStamps, -XX:+PrintGCTimeStamps | Acceptée | Refusée | Refusée | Rien : -Xlog gère les dates lui-même |
-XX:+UseGCLogFileRotation | Acceptée | Refusée | Refusée | Rien : -Xlog gère la rotation |
-XX:+PrintTenuringDistribution | Acceptée | Refusée | Refusée | -Xlog:gc+age*=debug |
-XX:+PrintGCDetails | Acceptée | Dépréciée : un avertissement | Dépréciée : un avertissement, puis -Xlog:gc* | -Xlog:gc* |
-Xloggc:<fichier> | Acceptée | Dépréciée : un avertissement | Dépréciée : un avertissement, puis -Xlog:gc:<fichier> | -Xlog:gc:<fichier> |
Les anciennes options de log du GC étaient donc déjà refusées en Java 11.
« Refusée » veut dire : « Unrecognized VM option », et la JVM ne démarre pas. Une option dépréciée, elle, marche encore, avec un avertissement :
[0.001s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
Un cas récent : -XX:+ZGenerational activait le mode générationnel de
ZGC en Java 21. Depuis Java 24, ce mode est le seul : en Java 25,
l’option est ignorée avec un avertissement, et -XX:+UseZGC suffit.
Tester une vieille option avant de la garder
Le plus simple est de lancer la JVM avec vos options et -version :
java -XX:+PrintGCDateStamps -Xmx2g -version
Si une option est refusée, vous le voyez tout de suite, sans lancer
l’application. La documentation de la commande java
a aussi trois listes : options dépréciées, obsolètes et supprimées. Mais
elles sont courtes : une option supprimée il y a longtemps n’y est plus
(-XX:PermSize, par exemple). Elle a aussi une table
qui donne l’équivalent -Xlog de chaque ancienne option de log du GC.
Sources : la documentation de la commande java et le
guide d’Oracle sur le garbage collector
(JDK 25), JEP 122 (PermGen),
JEP 271 (logs du GC avec -Xlog),
JEP 363 (CMS),
JDK-8173421 (ParNew),
JDK-8194086 (cgroups),
JEP 490 (ZGC).
À retenir
Gardez peu d’options, et sachez pourquoi chacune est là : la taille du heap, les logs du GC vers un fichier et le heap dump. Ne copiez pas une liste d’options d’un forum ou d’un vieux script sans la comprendre : au mieux, une partie ne sert plus à rien ; au pire, la JVM ne démarre pas.