Minor GC, Major GC, Full GC : des mots qu’on lit partout, mais pas toujours avec le sens que leur donne la documentation officielle.

La plupart des objets meurent jeunes. La JVM range donc les objets récents dans la Young, et ceux qui durent dans la Old. Un GC qui ne nettoie que la Young est rapide, et il passe souvent.

Pourquoi des générations

Le guide officiel d’Oracle sur le garbage collector part d’une observation : la plupart des objets ne vivent que très peu de temps. On l’appelle l’hypothèse générationnelle. Un objet créé pour une requête, une boucle ou un calcul devient inutile presque tout de suite.

Si la JVM sépare les objets récents des objets qui durent, elle peut nettoyer souvent la petite zone des objets récents, où presque tout est mort, sans parcourir tout le heap à chaque fois.

Le trajet d’un objet : Eden, Survivor, Old

Le trajet d'un objet dans le heap : il naît dans l'Eden, passe d'un Survivor à l'autre à chaque minor collection, puis il est promu dans la OldHeapYoungles objets récentsEdenles objets naissent iciobjetSurvivor 0les deux Survivor serventSurvivor 1à tour de rôle ; un est videOldles objets qui durentobjet1231Minor collection : l'objet vivant est copié de l'Eden vers un Survivor.2À chaque minor collection, il passe dans l'autre Survivor ; son âge augmente.3Après assez de copies (MaxTenuringThreshold, 15 par défaut) : promu dans la Old.

La Young est faite de trois zones : l’Eden et deux Survivor.

  1. Un objet naît presque toujours dans l’Eden.
  2. Quand la Young est pleine, le GC nettoie la Young. Les objets encore vivants sont copiés dans un Survivor ; l’Eden est alors vide.
  3. Au GC suivant, les objets vivants passent d’un Survivor à l’autre. Un des deux Survivor est toujours vide.
  4. Après un certain nombre de copies, ou s’il n’y a plus de place dans les Survivor, l’objet est copié dans la Old : on dit qu’il est promu.

Le nombre maximum de copies se règle avec -XX:MaxTenuringThreshold. Sa valeur par défaut est 15.

Minor, Major, Full : ce que dit vraiment la doc

Dans le guide d’Oracle :

  • une minor collection ne nettoie que la Young ;
  • une major collection nettoie tout le heap, quand la Old est pleine. Elle dure en général bien plus longtemps ;
  • le mot full GC apparaît, par exemple, pour un nettoyage complet demandé par System.gc().

Beaucoup d’articles disent autre chose : « Major GC = la Old seulement, Full GC = tout le heap ». Ce n’est pas la définition du guide officiel.

Et dans les logs, les mots dépendent du GC. G1, le GC par défaut, n’écrit jamais « minor » ni « major » :

[0.238s] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 35M->3M(64M) 2.406ms
[0.287s] GC(34) Pause Young (Mixed) (G1 Evacuation Pause) 60M->35M(64M) 0.215ms
[0.335s] GC(82) Pause Full (System.gc()) 53M->27M(64M) 3.010ms

Une pause Young (Normal) nettoie la Young. Une pause Young (Mixed) nettoie la Young et, en plus, une partie de la Old. ZGC, lui, écrit bien Minor Collection et Major Collection :

[0.246s] GC(3) Minor Collection (Allocation Rate) 52M(81%)->52M(81%) 0.008s
[0.503s] GC(152) Major Collection (System.gc()) 38M(59%)->30M(47%) 0.009s

Quand on lit un log, il faut donc d’abord savoir quel GC l’a écrit.

Quand G1 fait un Full GC

Avec G1, le Full GC est une solution de secours : il compacte tout le heap, et la doc prévient qu’il peut être très lent. Il arrive dans deux cas principaux :

  • le heap est trop plein : l’application crée des objets plus vite que G1 ne peut libérer de la place dans la Old ;
  • quelqu’un le demande : un appel à System.gc(), dans l’application ou dans un outil.

Un Full GC de temps en temps n’est pas grave. Des Full GC qui se répètent sont un signal : le heap est trop petit, ou il y a une fuite mémoire.

Ce qui a changé

Le heap de G1 : des régions de même taille ; les régions Eden, Survivor et Old sont mélangées, entre des régions libresHeap de G1des régions de même taille (4 Mo ici), mélangées en mémoireEOSOEOOEOOEEOOSOEOOEOEOOEOOOEOSOEEdenSSurvivorOOldlibreLa Young = les régions Eden et Survivor ; la Old = les régions Old.Une pause Mixed nettoie la Young et, en plus, une partie des régions Old.

Depuis Java 9, G1 est le GC par défaut. Il ne coupe plus le heap en deux grands blocs : il le découpe en régions de même taille (4 Mo sur une machine de 32 Go). La Young et la Old sont des ensembles de régions, qui ne se suivent pas forcément en mémoire.

Ce qu’on lit encoreCe qui est vrai aujourd’huiDepuis
Le heap est coupé en deux blocs fixes, la Young et la OldAvec G1, le heap est fait de régions de même taille ; la Young et la Old sont des ensembles de régionsJava 9 (G1 par défaut)
ZGC n’a pas de générationsZGC est générationnel : en option en Java 21, par défaut en Java 23, seul mode depuis Java 24 (-XX:+ZGenerational est ignoré, avec un avertissement)Java 21 à 24
Major GC = nettoyage de la OldDans le guide d’Oracle, une major collection nettoie tout le heap—
On règle la Young avec -Xmn ou -XX:NewRatioAvec G1, la doc conseille de ne pas le faire : cela désactive presque le contrôle des pauses—

Sources : le guide d’Oracle sur le garbage collector (JDK 25), JEP 248 (G1 par défaut), JEP 439, JEP 474 et JEP 490 (ZGC générationnel).

Les options, en bref

  • -Xmn : la taille de la Young (initiale et maximale) ;
  • -XX:NewRatio : le rapport entre la Old et la Young (2 par défaut : la Old est deux fois plus grande que la Young) ;
  • -XX:SurvivorRatio : le rapport entre l’Eden et un Survivor (8 par défaut) ;
  • -XX:MaxTenuringThreshold : le nombre de copies avant la promotion (15 par défaut).

Avec G1, mieux vaut ne pas fixer la taille de la Young : G1 l’ajuste lui-même pour tenir son objectif de pause.

En résumé

Les objets naissent dans l’Eden, survivent quelques GC dans les Survivor, et les plus durables finissent dans la Old. « Minor » veut dire la Young ; « Major », dans la doc d’Oracle, veut dire tout le heap. Avec G1, on parle plutôt de pauses Young, Mixed et Full, et le heap est fait de régions.

Pour aller plus loin