Dans cette série, on a déjà utilisé une demi-douzaine d’outils : les
logs du GC, jstat, plusieurs commandes jcmd, Eclipse MAT. Chacun
répond à une question précise, et chacun a un coût. Cet article les
range, puis montre JFR, l’outil le plus complet du JDK, sur un exemple :
trouver la méthode qui crée des millions d’objets.
Quel outil pour quelle question
| Question | Outil | Commande | Coût |
|---|---|---|---|
| Le GC fait-il des pauses longues ? Le heap après GC monte-t-il ? | Logs du GC | -Xlog:gc*:file=gc.log | à garder en production |
| Quelle zone se remplit, en direct ? | jstat | jstat -gcutil <pid> 1s | non indiqué ; « expérimental et non supporté » d’après sa doc |
| Combien de heap est utilisé ? | jcmd | jcmd <pid> GC.heap_info | moyen |
| Quelle classe grossit ? | jcmd | jcmd <pid> GC.class_histogram | élevé : lance un Full GC |
| Qui retient les objets ? | heap dump + Eclipse MAT | jcmd <pid> GC.heap_dump | élevé : un gros fichier |
| Le Metaspace grossit-il ? | jcmd | jcmd <pid> VM.metaspace | moyen |
| Que fait la mémoire native ? | jcmd + NMT | -XX:NativeMemoryTracking=summary, puis jcmd <pid> VM.native_memory | moyen |
| Que font les threads ? | jcmd | jcmd <pid> Thread.print | moyen |
| Quelle méthode alloue, où sont les pauses, dans le temps ? | JFR | -XX:StartFlightRecording, puis jfr view | faible avec la configuration default |
Le coût « moyen » ou « élevé » des commandes jcmd est celui qu’indique
leur documentation. Chacun de ces outils a déjà servi dans un article :
les logs du GC,
les OutOfMemoryError,
les fuites mémoire et
le heap dump avec Eclipse MAT.
JFR pas à pas
Java Flight Recorder (JFR) est inclus dans le JDK. Il enregistre des événements de la JVM et de l’application : GC, allocations, threads, méthodes les plus utilisées…
Enregistrer. Au démarrage :
java -XX:StartFlightRecording:filename=hot.jfr,settings=profile -jar app.jar
Ou sur une application déjà lancée : jcmd <pid> JFR.start, puis
jcmd <pid> JFR.dump filename=hot.jfr pour écrire l’enregistrement.
Choisir la configuration. D’après la documentation d’Oracle, la
configuration default est recommandée pour les enregistrements
continus, avec en général moins de 1 % de surcoût. La configuration
profile enregistre plus d’événements, comme les échantillons
d’allocation : elle sert pour analyser un problème. La doc ne donne pas
de chiffre pour son surcoût.
Lire. La commande jfr du JDK affiche des tableaux, sans interface
graphique :
jfr view allocation-by-site hot.jfr
jfr view gc hot.jfr
Avant Java 21 :
jfr viewn’existe pas. En Java 17, la commande répondjfr: unknown command 'view': il faut passer parjfr printou par JDK Mission Control.
Exemple : la méthode qui crée des millions d’objets
HotAllocation.java
construit 10 millions de libellés avec String.format. On l’enregistre
avec JFR, puis :
jfr view allocation-by-site hot.jfr
Method Allocation Pressure
--------------------------------------------------- -------------------
java.util.Formatter.parse(String) 24.70%
java.lang.Long.toString(long) 18.83%
java.lang.AbstractStringBuilder.<init>(int) 14.20%
java.util.Arrays.copyOfRange(byte[], int, int) 10.61%
java.util.ArrayList.grow(int) 10.35%
Formatter.parse crée le plus d’objets. Ce sont des échantillons :
les pourcentages changent un peu d’un lancement à l’autre, mais le haut
du tableau reste le même. Reste à trouver l’appelant dans notre code :
la pile d’un échantillon le donne.
jfr print --events jdk.ObjectAllocationSample --stack-depth 6 hot.jfr
stackTrace = [
java.util.Formatter.parse(String) line: 2810
java.util.Formatter.format(Locale, String, Object[]) line: 2744
java.util.Formatter.format(String, Object[]) line: 2698
java.lang.String.format(String, Object[]) line: 4455
HotAllocation.label(int) line: 16
HotAllocation.main(String[]) line: 24
Le coupable : String.format, appelé dans label(). Chaque appel analyse
le motif "order-%08d" et crée plusieurs objets. Dans
FixedAllocation.java,
les mêmes libellés sont construits avec un StringBuilder. Même
résultat, avec -Xmx256m et JFR actif :
HotAllocation | FixedAllocation | |
|---|---|---|
| Java 25 | 32 GC, 2,7 s | 9 GC, 0,7 s |
| Java 21 | 44 GC | 12 GC |
| Java 17 | 94 GC | 10 GC |
Moins d’objets créés, donc moins de GC : c’est le « débit d’allocation » de l’article sur les logs du GC.
jcmd, le couteau suisse
jcmd envoie une commande à une JVM qui tourne. jcmd <pid> help
liste toutes les commandes disponibles. Celles de la série :
GC.heap_info, GC.class_histogram, GC.heap_dump, GC.run,
VM.metaspace, VM.native_memory, Thread.print, et JFR.start /
JFR.dump. Sa documentation indique l’impact de chacune : à lire avant
de lancer une commande « High » sur une production chargée.
VisualVM et JDK Mission Control
Pour ceux qui préfèrent une fenêtre :
- VisualVM suit une JVM en direct (CPU, activité du GC, heap, Metaspace), affiche ses threads, prend et ouvre les heap dumps, et fait du profilage simple.
- JDK Mission Control
ouvre les fichiers
.jfret montre les mêmes données quejfr view, en graphiques.
Le même hot.jfr, ouvert dans JMC. JMC compte les allocations par
classe, jfr view allocation-by-site par méthode : les deux vues
viennent du même enregistrement.
![JDK Mission Control, page Memory : en haut, les allocations par classe (byte[] 50,6 %, Object[] 18,9 %, java.util.Formatter$FixedString 14,1 %) ; en bas, le heap en dents de scie, avec une ligne verticale par GC.](/blog/outils-jfr-jcmd-visualvm/images/jmc-memory_hu_2ae5dde1d8a9e1e6.webp)
Avant Java 9 : VisualVM était livré avec le JDK (
jvisualvm). Il a été retiré en Java 9 et se télécharge à part.
Les deux s’installent séparément. Les commandes de cet article, elles, sont déjà dans le JDK et marchent sur un serveur sans écran.
En production
- À garder en continu : les logs du GC dans un fichier, et JFR avec
la configuration
default. - Ponctuellement, en connaissant le coût :
GC.class_histogram(un Full GC),GC.heap_dump(un gros fichier, avec des données sensibles), JFR avecprofile. - Avant d’agir : relire la ligne « Impact » de la commande dans la
documentation de
jcmd.
Testé avec Java 17, 21 et 25 (Temurin 17.0.20, 21.0.12, 25.0.4), sur macOS ; les programmes compilent avec Java 8.
Sources : la documentation de
jcmd,
jfr
et jstat,
le guide de Flight Recorder sur ses configurations
(JDK 25), JDK-8306703
(jfr view) et JDK-8180002
(VisualVM retiré du JDK).