Lorsque vous gérez des applications à forte charge ou des flux de données massifs, il est courant de rencontrer des problèmes tels que des Full GC fréquents, une accumluation dans le Old Gen ou une chute de débit. Pour diagnostiquer ces symptômes, l'outil en ligne de commande intégré au JDK, jmap, s'avère essnetiel. Il permet d'obtenir une vue précise de la mémoire heap d'un processus Java.
Présentation de jmap
jmap (Java Memory Map) génère une cartographie de la mémoire d'un processus Java. Il aide à visualiser la répartition des zones heap, à inspecter les statistiques des objets en mémoire, et même à effectuer un dump complet pour une analyse hors ligne.
Commandes principales et leurs utilisations
A. Statistiques des objets : identifier les consommateurs de mémoire
Pour savoir quels objets occupent le plus d'espace, une première étape consiste à lister les classes par taille mémoire décroissante.
jmap -histo <pid> | sort -k3 -nr | head -n 20</pid>
- Objectif : Affiche les classes en mémoire triées par empreinte mémoire.
- Interprétation : Un grand nombre d'instances de classes comme
java.util.TreeMap$Entryou des objets métier peut indiquer une fuite mémoire ou une configuration excessive du cache.
Exemple avec un PID 2856 :
$ jmap -histo 2856 | sort -k3 -nr | head -n 15
num #instances #bytes class name (module)
-------------------------------------------------------
1: 345000000 16560000000 com.example.data.RecordEntity
2: 344980000 13799200000 java.util.TreeMap$Entry (java.base)
3: 400000000 9600000000 java.lang.String (java.base)
4: 50000000 8800000000 [Ljava.util.TreeMap$Entry; (java.base)
5: 20000000 3200000000 java.util.concurrent.ConcurrentHashMap$Node (java.base)
6: 850000 136000000 [B (java.base)
7: 500000 40000000 com.example.cache.CacheEntry
8: 1000000 32000000 java.util.LinkedList$Node (java.base)
9: 200000 16000000 com.google.gson.JsonObject
10: 50000 8000000 [C (java.base)
Conseil : Utiliser jmap -histo:live <pid></pid> pour ne compter que les objets vivants. Cela déclenche un Full GC, ce qui peut causer une pause, mais permet d'identifier les objets non récupérables.
B. Vue d'ensemble des zones heap : évaluer l'utilisation de la mémoire
Pour comprendre l'état des zones heap (Eden, Survivor, Old Gen), on peut inspecter leur utilisation.
sudo -u <utilisateur> jmap -heap <pid></pid></utilisateur>
Alternativement, avec jcmd (recommandé pour JDK 9+) :
jcmd <pid> GC.heap_info</pid>
Indicateurs clés :
- Old Generation : Si la valeur
usedapproche 100%, cela indique une saturation, causant des pauses (STW) et des ralentissements. - MaxHeapSize : Vérifier que la taille maximale allouée correspond aux attentes.
Exemple de sortie (adapté pour un JVM G1) :
$ sudo -u appuser jmap -heap 2856
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 8589934592 (8192.0MB)
NewSize = 268435456 (256.0MB)
MaxNewSize = 5152702464 (4912.0MB)
OldSize = 536870912 (512.0MB)
G1HeapRegionSize = 16777216 (16.0MB)
Heap Usage:
G1 Heap:
regions = 512
capacity = 8589934592 (8192.0MB)
used = 12884901888 (12288.0MB)
free = -4294967296 (-4096.0MB)
150.0% used
G1 Old Generation:
regions = 200
capacity = 3355443200 (3200.0MB)
used = 3288334336 (3136.0MB)
free = 67108864 (64.0MB)
98.0% used
Dans cet exemple, l'Old Generation est presque pleine, ce qui pourrait entraîner des problèmes de performance.
C. Dump heap pour analyse approfondie
Quand les relations entre objets sont complexes, un dump heap permet une inspection détaillée avec des outils externes.
jmap -dump:format=b,file=dump_heap.bin <pid></pid>
Étapes suivantes : Transférer le fichier dump_heap.bin localement et l'analyser avec Eclipse MAT ou JProfiler. La fonctionnalité "Path to GC Roots" aide à retrouver les références problématiques, comme des collections statiques non libérées.
Étude de cas : diagnostic d'un ralentissement lors de sauvegardes
Dans un scénario réel, l'analyse via jmap -histo a révélé 250 millions d'objets HashMap$Node, consommant 20 Go d'espace heap.
- Symptôme : Les opérations de sauvegarde (backup) présentaient des temps de réponse élevés.
- Constat avec jmap : L'Old Generation était saturée par ces nœuds.
- Explication : La saturation de l'Old Generation augmentait le temps des cycles GC, impactant les threads de sauvegarde.
- Remédiation :
- Passage de
HashMapàLinkedHashMappour optimiser l'éviction du cache grâce à son itérateur O(1). - Réduction de la taille des buckets pour limiter le nombre d'objets.
- Passage de
Des profils de flamme ont confirmé que le GC consommait une part significative des ressources.
Précautions d'utilisation de jmap
- Environnement de production : Éviter
jmap -histo:liveetjmap -dump:livequi déclenchent des Full GC. Sur des systèmes avec des heaps de plus de 100 Go, cela peut entraîner des indisponibilités de plusieurs minutes. - Permissions : Exécuter les commandes avec les mêmes droits que le processus Java cible (ex. :
sudo -u <utilisateur> jmap ...</utilisateur>). - Évolutions du JDK : À partir de JDK 9, privilégier
jcmd <pid> GC.heap_dump</pid>pour les dumps, car il offre généralement de meilelures performances.