jmap : Une introduction à l'analyse mémoire des applications Java

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$Entry ou 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 used approche 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.

  1. Symptôme : Les opérations de sauvegarde (backup) présentaient des temps de réponse élevés.
  2. Constat avec jmap : L'Old Generation était saturée par ces nœuds.
  3. Explication : La saturation de l'Old Generation augmentait le temps des cycles GC, impactant les threads de sauvegarde.
  4. Remédiation :
    • Passage de HashMap à LinkedHashMap pour 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.

Des profils de flamme ont confirmé que le GC consommait une part significative des ressources.

Précautions d'utilisation de jmap

  1. Environnement de production : Éviter jmap -histo:live et jmap -dump:live qui 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.
  2. Permissions : Exécuter les commandes avec les mêmes droits que le processus Java cible (ex. : sudo -u <utilisateur> jmap ...</utilisateur>).
  3. É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.

Étiquettes: jmap Java JVM Garbage Collection heap dump

Publié le 21 juin à 01h55