Java

Service Java lent ou figé : combiner thread dumps et JFR

Choisir entre thread dump, captures répétées et fenêtre JFR puis croiser les indices pour guider le diagnostic.

Thread dump pour observer un instant; JFR pour analyser événements et échantillons dans le temps.

Une requête reste bloquée, le CPU grimpe ou la latence se dégrade pendant plusieurs minutes. La collecte utile dépend de la durée du symptôme et de la question à trancher. Un thread dump montre un instant; plusieurs dumps révèlent ce qui persiste; JFR donne une fenêtre d’événements. On peut commencer par l’un et compléter avec l’autre.

Choisir la collecte selon le symptôme

Symptôme Question immédiate Thread dump JFR Stratégie utile
Application figée maintenant Où les threads attendent-ils ? Un dump donne les piles, états et verrous visibles à cet instant. Une fenêtre courte peut révéler l’activité récente, si elle a été enregistrée. Capturer d’abord un dump; démarrer JFR si le dump ne distingue pas attente durable et activité transitoire.
Latence qui dure Une pile ou une contention revient-elle ? Plusieurs dumps espacés montrent les piles et états persistants. Les événements et échantillons replacent l’activité dans le temps. Faire une série de dumps et une fenêtre JFR qui couvre le même intervalle.
CPU élevé Quels threads consomment le CPU et que font-ils ? Des piles répétées aident à localiser les threads actifs, sans mesurer leur consommation sur toute la période. Les échantillons d’exécution aident à comparer l’activité au cours de la fenêtre. Utiliser JFR comme vue temporelle; ajouter des dumps si une pile précise doit être examinée.
Blocage intermittent ou contention Le même verrou ou la même attente revient-il ? Plusieurs captures peuvent montrer l’attente et les propriétaires de verrous visibles. JFR peut apporter la chronologie d’événements de contention configurés. Collecter les deux pendant le symptôme si celui-ci dure assez longtemps.
Dégradation passée ou intermittente Que s’est-il passé pendant la période ? Un dump pris après coup ne reconstitue pas l’état précédent. Un enregistrement déjà actif peut conserver des événements de la période. Préparer une fenêtre JFR bornée; prendre un dump si le symptôme se reproduit.

Le dump apporte une photographie des threads. Une série aide à distinguer une attente stable d’un instant passager. JFR apporte une chronologie, selon les événements activés et les données collectées. Croisez ces observations avec le code, les journaux et le contexte de la requête.

Commencer simple puis compléter

Pour un gel en cours, commencez par un thread dump et notez l’heure ainsi que le symptôme. Si plusieurs threads attendent sur le même verrou ou si la même pile revient, capturez à nouveau après quelques secondes. Si les états changent rapidement ou si la latence dépend d’une période plus longue, ajoutez une fenêtre JFR courte. Rapprochez les horodatages pour vérifier si les indices concordent.

Pour le CPU, JFR est souvent plus utile pour comparer l’activité dans le temps. Un dump pris pendant le pic peut compléter l’enregistrement en montrant les piles à cet instant. Pour suivre un incident intermittent, démarrez JFR avant l’occurrence et capturez des dumps pendant le symptôme.

Préparer l’accès à la JVM

Les exemples ciblent HotSpot avec les outils du JDK 25. Les commandes exposées dépendent de la JVM et de sa configuration. Vérifiez que jcmd et jfr sont disponibles, que l’outil tourne sur la même machine et sous les mêmes identifiants effectifs que la JVM. Choisissez aussi un emplacement protégé où les fichiers peuvent être écrits.

Listez les JVM visibles puis demandez à la cible ses commandes disponibles :

jcmd -l
jcmd <pid> help
jcmd <pid> help Thread.print
jcmd <pid> help JFR.start

Remplacez <pid> par l’identifiant relevé dans la première commande. Ne supposez pas qu’une commande existe avant d’avoir interrogé la JVM concernée.

Capturer et comparer des threads

Oracle SE 25 classe l’impact de Thread.print comme moyen selon le nombre de threads. En incident, espacez les captures et adaptez leur fréquence à la JVM : la collecte a un coût. Une capture ponctuelle s’effectue ainsi :

jcmd <pid> Thread.print -l > thread-1.txt

-l demande les informations sur les verrous java.util.concurrent, lorsque cette option est prise en charge. Pour comparer l’évolution, associez l’heure de début à chaque fichier plutôt que d’imprimer l’heure à part dans le terminal :

pid=12345 # remplacer par le PID repéré avec jcmd -l
diagnostics_dir=$(mktemp -d /tmp/java-diag.XXXXXX)
for n in 1 2 3; do
  file="$diagnostics_dir/thread-$n.txt"
  if {
    printf '# Capture commencée à %s\n' "$(date)"
    jcmd "$pid" Thread.print -l
  } > "$file" 2>&1; then
    :
  else
    status=$?
    printf '# Échec de jcmd, code %s; voir le diagnostic ci-dessus.\n' "$status" >> "$file"
  fi
  sleep 10
done

Chaque fichier contient son horodatage, la sortie de jcmd et, en cas d’échec, son diagnostic et code retour. Dans les captures réussies, rapprochez le symptôme des états, piles et verrous visibles puis comparez les mêmes threads. Une pile répétée oriente l’enquête vers une attente persistante ou une activité longue; croisez-la avec le code et le contexte de la requête.

Enregistrer une fenêtre JFR

Si la JVM expose les commandes JFR, vous pouvez démarrer un enregistrement borné puis examiner le fichier produit :

pid=12345 # remplacer par le PID de la JVM
diagnostics_dir=$(mktemp -d /tmp/jfr-triage.XXXXXX)
recording="$diagnostics_dir/triage-${pid}.jfr"
if jcmd "$pid" JFR.start name=triage settings=profile duration=60s filename="$recording"; then
  sleep 65
  if [ -s "$recording" ]; then
    jfr summary "$recording"
  else
    printf 'Capture absente ou vide : examiner le diagnostic JVM.\n' >&2
  fi
else
  printf 'Démarrage JFR refusé : examiner le diagnostic avant de continuer.\n' >&2
fi

Vérifiez au préalable l’aide JFR.start de la cible. mktemp -d crée ici un répertoire temporaire privé et absolu; choisissez un emplacement auquel le processus JVM peut écrire et que jfr peut lire. recording est transmis tel quel à jcmd puis à jfr summary. Protégez ce fichier de diagnostic. L’enregistrement dure 60 secondes; attendez sa fin avant de résumer le fichier. Le profil profile collecte plus de données que default.jfc et peut avoir davantage d’impact; gardez cette fenêtre courte et consultez la documentation du JDK ciblé. Rapprochez la chronologie d’événements du blocage observé et des autres indices.

Les captures peuvent contenir des noms de threads, des piles, des chemins et d’autres détails applicatifs. Limitez leur accès, évitez de les joindre telles quelles à un ticket public et expurgez tout extrait partagé.

Sources techniques

  1. The jcmd CommandOracle
  2. Troubleshooting Guide, Java SE 25Oracle
  3. The jfr CommandOracle