Activer le log du GC est facile. Le lire l’est moins : une ligne de G1 contient une dizaine d’informations, et les mots changent d’une version de Java à l’autre.

Cet article lit de vraies lignes, produites par trois petits programmes du dépôt public java-gc-lab : une charge normale, des objets très gros, et un heap presque plein.

Activer le log

java -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar app.jar
  • gc* : tous les messages du GC (gc seul donne une ligne par GC) ;
  • file=gc.log : dans un fichier, avec une rotation déjà intégrée (5 fichiers d’environ 20 Mo par défaut) ;
  • time,uptime,level,tags : les « décorations » au début de chaque ligne : la date et l’heure, le temps depuis le démarrage de la JVM, le niveau et les tags. Sans rien préciser, la JVM met uptime, level et tags.

Une ligne de pause, avec ces décorations :

[2026-10-22T23:23:46.842+0200][0.356s][info][gc          ] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 117M->24M(256M) 1.660ms

L’heure exacte (time) sert à relier une pause à un événement de l’application, par exemple une requête lente dans un autre log.

Une ligne, mot par mot

Une ligne de log G1 en Java 25, découpée en cinq parties : GC(1), le numéro du GC ; Pause Young (Normal), le type de pause ; (G1 Evacuation Pause), la cause ; 117M->23M(256M), le heap avant et après le GC et sa taille ; 5.790ms, la durée de la pauseUne ligne de log G1Java 25, -Xlog:gcGC(1)n° du GCPause Young (Normal)type de pause(G1 Evacuation Pause)cause du GC117M->23M(256M)heap avant -> après (taille)5.790msdurée de la pause
PartieSens
[0.469s]le temps depuis le démarrage de la JVM (uptime)
GC(1)le numéro du GC : toutes les lignes d’un même GC ont le même
Pause Young (Normal)le type de pause : ici une pause young normale. On voit aussi Concurrent Start, Prepare Mixed, Mixed, Remark, Cleanup et Full
(G1 Evacuation Pause)la cause du GC, ici le cas courant. Autres causes : G1 Humongous Allocation, System.gc(), G1 Compaction Pause (Full GC)
117M->23M(256M)le heap utilisé avant le GC, après le GC, et la taille du heap
5.790msla durée de la pause : l’application est arrêtée pendant ce temps

Les mots young, mixed et full sont expliqués dans l’article sur les générations.

Les 4 chiffres à suivre

Voici le début et la fin du log de NormalLoad.java, une application qui garde environ 16 Mo de données et crée des objets temporaires en continu :

java -Xmx256m -Xlog:gc gc-logs/NormalLoad.java
[0.341s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 47M->18M(256M) 3.090ms
[0.469s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 117M->23M(256M) 5.790ms
[0.646s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 149M->24M(256M) 4.129ms
…
[3.240s][info][gc] GC(15) Pause Young (Normal) (G1 Evacuation Pause) 171M->24M(256M) 0.635ms
[3.478s][info][gc] GC(16) Pause Young (Normal) (G1 Evacuation Pause) 171M->24M(256M) 12.874ms
  1. La fréquence des GC : 17 GC en un peu plus de 3 secondes, environ 5 par seconde. Elle monte quand l’application crée plus d’objets ou quand le heap est trop petit.
  2. La durée des pauses : de 0,4 à 12,9 ms ici. On regarde surtout les plus longues : ce sont elles que voient les utilisateurs.
  3. Le heap après le GC : 23 à 24 Mo, stable. C’est à peu près la taille des données vivantes. S’il monte d’un GC à l’autre, c’est une fuite (voir l’article sur les OutOfMemoryError).
  4. Le débit d’allocation : le heap avant un GC moins le heap après le GC précédent, divisé par le temps entre les deux. Entre GC(0) et GC(1) : (117 − 18) Mo en 0,128 s, soit environ 770 Mo/s. Aucune ligne ne le donne directement : il faut le calculer.

Ce qui doit alerter

Des objets humongous

java -Xmx256m -Xlog:gc gc-logs/Humongous.java
[0.306s][info][gc] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 146M->9M(256M) 1.583ms

Pour G1, un objet « humongous » fait au moins la moitié d’une région. Il ne passe pas par l’Eden : il reçoit ses propres régions, directement dans la Old. Ici, le programme crée des tableaux de 2 Mo avec des régions de 1 Mo (Heap Region Size: 1M au démarrage avec -Xlog:gc*), et c’est l’allocation elle-même qui lance le GC. Beaucoup de lignes G1 Humongous Allocation indiquent de gros tableaux ou de gros buffers créés souvent.

Un échec d’évacuation, puis Pause Full

java -Xmx64m -Xlog:gc gc-logs/NearTheLimit.java
[0.295s][info][gc] GC(5) Pause Young (Prepare Mixed) (G1 Evacuation Pause) (Evacuation Failure: Allocation) 61M->47M(64M) 0.250ms
[1.348s][info][gc] GC(175) Pause Full (G1 Compaction Pause) 62M->45M(64M) 5.691ms

Le programme garde environ 30 Mo de données dans un heap de 64 Mo. G1 ne trouve plus assez de place pour copier les objets vivants : c’est un échec d’évacuation. La raison est Allocation (pas de place) ou Pinned (un objet bloqué par du code natif). Si un GC ne libère plus rien, G1 lance un Full GC, qui compacte tout le heap et peut être très lent.

Avant Java 18 : l’échec d’évacuation s’appelait to-space exhausted. En Java 11 et 17, c’était une ligne à part (GC(7) To-space exhausted) ; en Java 8, la fin de la ligne de pause. Java 21 écrit (Evacuation Failure), et Java 25 ajoute la raison.

Une ligne Pause Full avec G1, quand elle ne vient pas de System.gc(), est toujours à regarder : le heap est trop petit pour les données vivantes, ou il y a une fuite.

Des pauses qui s’allongent

Si le dernier chiffre des lignes monte au fil des heures, avec le même type de pause, le GC a de plus en plus de travail : plus de données vivantes à copier, ou un heap qui se remplit. C’est un signe à suivre de près.

Les outils

  • JFR (Java Flight Recorder), dans le JDK : on le lance avec -XX:StartFlightRecording, et on lit l’enregistrement avec JDK Mission Control. Il montre les pauses du GC avec le reste de l’application. Les détails sont dans l’article sur les outils.
  • Des outils tiers lisent aussi les fichiers de log et en font des graphiques, par exemple GCeasy (un service en ligne) ou GCViewer (open source).

Ce qui a changé

Ce qu’on lit encoreCe qui est vrai aujourd’huiDepuis
-XX:+PrintGCDetails, -Xloggc:gc.log-Xlog:gc*, -Xlog:gc*:file=gc.log (les anciennes options sont dépréciées ou refusées, voir les options JVM)Java 9
to-space exhaustedEvacuation Failure, puis Evacuation Failure: <raison>Java 18 (JDK-8269022), la raison en Java 25 dans mes tests
-XX:+PrintGCDateStamps pour avoir l’heurela décoration time de -XlogJava 9

Testé avec Java 8 (Corretto 8u504), 11, 17, 21 et 25 (Temurin 11.0.32, 17.0.20, 21.0.9, 25.0.4), sur macOS, avec G1.

Sources : la documentation de la commande java (JDK 25, -Xlog), le chapitre G1 du guide d’Oracle sur le GC (objets humongous, échec d’évacuation, Full GC), JDK-8269022 (le nouveau nom de l’échec d’évacuation).

Pour aller plus loin