Votre vieille configuration Java ne démarre plus en Java 17 ? La réponse est dans la mémoire de la JVM.
En Java, chaque thread a sa stack, pour ses variables locales. Les objets vivent dans le heap, la zone que le garbage collector nettoie. Les classes sont décrites dans le Metaspace, hors du heap. Et la JVM utilise encore d’autres zones de mémoire, qu’on oublie souvent.
Trois zones à connaître
La stack : une par thread
Chaque thread a sa propre stack. À chaque appel de méthode, la JVM y ajoute une frame : les paramètres et les variables locales de la méthode. Quand la méthode se termine, sa frame disparaît. Pas besoin du garbage collector pour libérer cette mémoire.
Une variable locale de type objet ne contient pas l’objet : elle contient une référence vers un objet du heap.
Si les appels s’empilent trop, par exemple avec une récursion sans fin,
la stack déborde : StackOverflowError. Sa taille se règle avec -Xss.
La valeur par défaut dépend de la plateforme : 1 Mo sur Linux x64, 2 Mo
sur Linux et macOS en ARM (Aarch64).
Le heap : les objets, et le travail du GC
Les objets créés avec new vont dans le heap, partagé par tous les
threads. C’est la zone du garbage collector : il cherche les objets que
plus rien ne référence, et il récupère leur place.
Sa taille maximale se règle avec -Xmx. Sans cette option, la JVM fixe
en général le maximum à 25 % de la mémoire. Sur une machine de 32 Go de
mémoire, cela donne un heap maximum de 8 Go.
Le heap contient aussi les chaînes internées (String.intern()) et les
champs statiques des classes.
Le Metaspace : les classes, hors du heap
Pour chaque classe chargée, la JVM garde sa description : ses champs, ses méthodes… Ces métadonnées sont dans le Metaspace, en mémoire native, hors du heap.
Par défaut, sa taille n’est pas limitée : il grandit avec le nombre de
classes chargées. On peut fixer un maximum avec -XX:MaxMetaspaceSize.
Et ce qu’on oublie souvent
Le heap et le Metaspace ne sont pas les seules zones. La JVM utilise aussi de la mémoire native pour :
- les stacks des threads : une par thread, donc beaucoup de threads, c’est beaucoup de mémoire ;
- le code cache : le code machine produit par le compilateur JIT, 240 Mo au maximum par défaut ;
- la direct memory : les buffers alloués hors du heap avec
ByteBuffer.allocateDirect(). Sans-XX:MaxDirectMemorySize, la JVM choisit la limite elle-même ; - les structures internes du garbage collector, du compilateur, etc.
Ce qui a changé
Prenons un développeur qui reprend une vieille configuration, trouvée dans un ancien projet ou sur un forum :
java -Xms512m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m -jar app.jar
Avec Java 8, l’application démarre. La JVM affiche seulement deux avertissements, faciles à rater dans les logs :
OpenJDK 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Avec Java 17, 21 ou 25, elle ne démarre plus :
Unrecognized VM option 'PermSize=128m'
Error: Could not create the Java Virtual Machine.
Et la JVM s’arrête à la première option inconnue : on retire
PermSize, on relance, et c’est au tour de MaxPermSize.
La raison : PermGen n’existe plus depuis Java 8.
| Ce qu’on lit encore | Ce qui est vrai aujourd’hui | Depuis |
|---|---|---|
| Les classes sont dans PermGen, une zone de taille fixe | Leurs métadonnées sont dans le Metaspace, en mémoire native, sans limite par défaut | Java 8 |
On règle PermGen avec -XX:PermSize et -XX:MaxPermSize | Options ignorées avec un avertissement, puis refusées : la JVM ne démarre pas. À la place : -XX:MetaspaceSize et -XX:MaxMetaspaceSize | Ignorées jusqu’à Java 16, refusées depuis Java 17 |
OutOfMemoryError: PermGen space | PermGen n’existe plus, cette erreur non plus | Java 8 |
| Les chaînes internées et les champs statiques sont dans PermGen | Ils sont dans le heap | Java 8 au plus tard |
Sources : JEP 122, Remove the Permanent Generation,
et la documentation de la commande java de JDK 17
(section « Removed Java Options »).
Deux erreurs courantes
« Tout est dans le heap »
Le heap contient les objets, avec les chaînes internées et les champs
statiques. Les classes, le code compilé, les stacks des threads et les
buffers directs sont ailleurs, en mémoire native. -Xmx ne les limite
pas.
« -Xmx, c’est la mémoire de mon application »
-Xmx ne limite que le heap. Le processus Java utilise toujours plus.
Par exemple, avec Java 25 et -Xmx512m, la JVM réserve déjà environ
598 Mo de mémoire utilisable (« committed » dans le Native Memory
Tracking), juste pour afficher sa version. Pour dimensionner une machine
ou un conteneur, il faut compter tout le processus, pas seulement le
heap.
En résumé
Trois zones à retenir : la stack de chaque thread, le heap des objets et
le Metaspace des classes. Seul le heap est limité par -Xmx, et il n’est
qu’une partie de la mémoire du processus. Si une vieille configuration
parle de PermGen, il est temps de la nettoyer.