Méthodes courantes pour obtenir des vidages de thread Java

1. Introduction au vidage de thread

Un thread dump (Thread Dump) est un instantané de toutes les informations sur l'état des threads dans la JVM.

Les thread dumps utilisent généralement un format texte, qui peut être enregistré dans un fichier texte, puis visualisé et analysé manuellement, ou automatiquement analysé à l'aide d'outils/API.

Le modèle de thread en Java utilise directement le modèle de planification des threads du système d'exploitation et n'effectue qu'une simple encapsulation.

Pile d'appels de thread, également connue sous le nom de pile d'appels de méthode. Par exemple, dans le processus d'exécution d'un programme, il existe une série de chaînes d'appels de méthodes : obj1.method2appelée obj2.methodBet obj2.methodBappelée à nouveau obj3.methodC. L'état de chaque thread peut être représenté par cette pile d'appels.

Les thread dumps montrent le comportement des threads individuels et sont très utiles pour diagnostiquer et résoudre les problèmes.

Ci-dessous, nous utilisons des exemples spécifiques pour démontrer divers outils permettant d'obtenir des thread dumps Java et comment les utiliser.

2. Utilisez les outils fournis avec le JDK

Nous utilisons généralement les outils de ligne de commande fournis avec le JDK pour obtenir des thread dumps d'applications Java. Ces outils se trouvent dans le dossier bin du répertoire principal du JDK.

Alors, configurez simplement le chemin PATH. Si vous ne savez pas comment le configurer, vous pouvez vous référer à : Préparation de l'environnement JDK

2.1 outil jstack

jstack est un outil de ligne de commande intégré du JDK, spécialement utilisé pour afficher l'état des threads et peut également être utilisé pour effectuer un vidage de thread.

Généralement, recherchez d'abord le pid correspondant au processus Java via la commande jpsou ps, puis affichez le thread dump via le pid dans la console. Bien entendu, nous pouvons également rediriger la sortie vers un fichier.

Le format de paramètre de base pour obtenir des thread dumps à l'aide de l'outil jstack est :

jstack [-F] [-l] [-m] <pid>

Veuillez consulter la démonstration spécifique ci-dessous :

# 1. 查看帮助信息
jstack -help

Le résultat ressemble à ceci :

Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

Les options de paramètres correspondantes sont facultatives. La signification spécifique est la suivante :

  • -Foption, pour appliquer le thread dump ; parfois, jstack pidil se fige, vous pouvez ajouter -Fl'indicateur
  • -loption, recherchera les synchroniseurs et les verrous de ressources appartenant à la mémoire tas
  • -moption, imprimer en plus les cadres de pile natifs (C et C++)

Par exemple, pour effectuer un thread dump et afficher le résultat dans un fichier :

jstack -F 17264 > /tmp/threaddump.txt

Utilisez jpsla commande pour obtenir le pid du processus Java local.

2.2 Contrôle de mission Java

Java Mission Control (JMC) est un outil d'interface graphique client permettant de collecter et d'analyser diverses données d'applications Java.
Lorsque JMC est démarré, il affiche d'abord une liste des processus Java exécutés sur l'ordinateur local. Bien entendu, vous pouvez également vous connecter à un processus Java distant via JMC.

Vous pouvez cliquer avec le bouton droit sur le processus correspondant et sélectionner « Démarrer l'enregistrement du vol (démarrer l'enregistrement du vol) ». Après avoir terminé, l'onglet "Threads" affiche un "thread dump" :

insérer la description de l'image ici

2.3 jvisualvm

jvisualvm est un outil d'interface graphique client, simple et pratique, qui peut être utilisé pour surveiller les applications Java, dépanner et effectuer une analyse des performances sur JVM.

Peut également être utilisé pour obtenir des thread dumps. Faites un clic droit sur le processus Java, sélectionnez l'option « Thread Dump » et vous pourrez créer un thread dump, qui s'ouvrira automatiquement dans un nouvel onglet une fois terminé :

insérer la description de l'image ici

2.4 jcmd

L'outil jcmd envoie essentiellement une chaîne de commandes à la JVM cible. Bien que de nombreuses fonctionnalités soient prises en charge, il ne prend pas en charge la connexion à des JVM distantes : il ne peut être utilisé que sur la machine locale du processus Java.

L'une des commandes consiste Thread.printà effectuer un thread dump, un exemple d'utilisation est le suivant :

jcmd 17264 Thread.print

2.5 jconsole

L'outil jconsole peut également afficher les traces de la pile de threads.
Ouvrez jconsole et connectez-vous au processus Java en cours d'exécution, accédez à l'onglet "Threads", vous pouvez afficher la trace de pile de chaque thread :

insérer la description de l'image ici

2.6 Résumé

Il s'avère que les thread dumps peuvent être obtenus à l'aide de nombreux outils du JDK. Récapitulons et résumons leurs avantages et inconvénients :

  • jstack: L'outil le plus simple et le plus pratique pour obtenir des thread dumps ; après Java 8, l'outil jcmd peut être utilisé à la place ;
  • jmc: Amélioration de l'analyse des performances du JDK et de l'outil de diagnostic des problèmes. La surcharge de profilage avec cet outil est très faible.
  • jvisualvm: Un outil d'analyse open source léger avec une excellente interface graphique et prend en charge divers plug-ins fonctionnels puissants.
  • jcmd: Outil natif très puissant, supporte Java 8 et supérieur. Intégrez les fonctions de plusieurs outils, tels que : capturer le thread dump (jstack), le heap dump (jmap), afficher les propriétés du système et afficher les paramètres de ligne de commande (jinfo).
  • jconsole: Peut également être utilisé pour afficher les informations de trace de la pile de threads.

3. Utiliser les commandes Linux

Sur les serveurs d'applications d'entreprise, seul JRE peut être installé pour des raisons de sécurité. Pour le moment, ces outils intégrés au JDK ne peuvent pas être utilisés.
Mais il existe toujours un moyen d'obtenir un thread dump.

3.1 Utilisation kill -3de la commande

Dans des systèmes tels qu'Unix/Linux, vous pouvez utiliser killla commande pour obtenir des thread dumps, et le principe d'implémentation sous-jacent est d' kill()envoyer des paramètres de signal au processus via des appels système. Ce qu’il faut envoyer ici, c’est -3le signal.

jpsGénéralement, recherchez d'abord le pid correspondant au processus JAVA via , kill -3l'exemple d'utilisation est le suivant :

kill -3 17264

3.2 Ctrl + Break(Windows)

Dans la fenêtre de ligne de commande du système d'exploitation Windows, vous pouvez utiliser la combinaison de touches Ctrl + Breakpour obtenir un thread dump. Bien entendu, il faut d'abord accéder à la fenêtre de la console où le programme Java a été lancé, puis appuyer simultanément sur CTRLla touche et la touche .Break

A noter que certains claviers ne disposent pas de Breaktouche " ".
Dans ce cas, CTRLles touches SHIFT, et Pausepeuvent être utilisées en combinaison.

Les deux commandes peuvent imprimer un thread dump sur la console.

4. Utilisez ThreadMxBean par programme

La technologie JMX prend en charge une variété d'opérations sophistiquées. Un thread dump peut ThreadMxBeanêtre effectué avec .

L'exemple de code est le suivant :

private static String threadDump(boolean lockedMonitors, boolean lockedSynchronizers) {
    
    
    StringBuffer threadDump = new StringBuffer(System.lineSeparator());
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    for(ThreadInfo threadInfo : threadMXBean.dumpAllThreads(lockedMonitors, lockedSynchronizers)) {
    
    
        threadDump.append(threadInfo.toString());
    }
    return threadDump.toString();
}

Ce que fait le code ci-dessus est très simple, faites d'abord ManagementFactorypasser ThreadMxBeanl'objet via .
Les paramètres booléens de la méthode lockedMonitorset lockedSynchronizers, indiquent s'il faut exporter les verrous de synchronisation et de surveillance détenus.

Cependant, cette approche présente certains inconvénients :

    1. Les performances ne sont pas très bonnes et consomment beaucoup de ressources.
    1. threadDump.toString()La méthode ne produira que jusqu'à 8 cadres de pile (MAX_FRAMES = 8 ); vous pouvez copier le code toString et le modifier/filtrer vous-même.
    1. Les threads natifs (tels que les threads GC) ne seront pas vidés.

plan alternatif :

    1. Appelez jstack via Runtime pour obtenir des informations sur le thread dump ; en cas d'échec, il reviendra en mode JMX ;

Une partie du code :


    public static String jStackThreadDump() {
    
    
        // 获取当前JVM进程的pid
        long currentPid = currentPid();
        // 组装命令
        String cmdarray[] = {
    
    
                "jstack",
                "" + currentPid
        };
        ProcessBuilder builder = new ProcessBuilder(cmdarray);
        String threadDump = "";
        try {
    
    
            Process p = builder.start();
            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            StringJoiner sj = new StringJoiner(System.lineSeparator());
            reader.lines().iterator().forEachRemaining(sj::add);
            threadDump = sj.toString();
            p.waitFor();
            p.destroy();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        return threadDump;
    }

    public static long currentPid() {
    
    
        final long fallback = -1;
        final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
        final int index = jvmName.indexOf("@");
        if (index < 1) {
    
    
            return fallback;
        }
        String pid = jvmName.substring(0, index);
        if (null != pid && pid.matches("\\d+")) {
    
    
            return Long.parseLong(pid);
        }
        return fallback;
    }

5. Résumé

Nous avons montré différentes méthodes pour obtenir des thread dumps avec des exemples concrets.

Il présente d'abord divers outils intégrés au JDK,
puis discute de la méthode de ligne de commande et
enfin présente la méthode de programmation JMX.

Pour un exemple de code complet, veuillez vous référer au référentiel GitHub .

6. Annexe : État du fil de discussion et exemple de code

L'état du fil de discussion peut être référencé Thread.State, notamment :

  • NEW: Non démarré ; Par exemple, la méthode start n'a pas encore été exécutée (terminée) ;
  • RUNNABLE: Statut exécutable ; c'est la perspective de la JVM, et l'utilisation ou non du CPU dépend de la planification du système d'exploitation ;
  • BLOCKED: État bloqué ; tel que l'entrée dans une méthode de synchronisation/un bloc de synchronisation, l'attente d'une ressource de verrouillage ;
  • WAITING: En attente de ressources de verrouillage, telles que Unsafe.park(), Object.wait()etc.
  • TIMED_WAITING: Attente limitée dans le temps pour les ressources de verrouillage, telles que Unsafe.park(), Object.wait()etc.
  • TERMINATED : terminé ; l'exécution de la tâche du thread est terminée.

Code d'essai :


import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 简单模拟线程的各种状态
public class ThreadStateTest implements Runnable {
    
    
    public final Lock lock = new ReentrantLock(true);
    public final CountDownLatch beforeMonitorLatch = new CountDownLatch(1);
    public final CountDownLatch beforeLockLatch = new CountDownLatch(1);
    public final CountDownLatch toSleepLatch = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {
    
    
        // Runnable task
        ThreadStateTest task = new ThreadStateTest();
        // 新创建线程对象
        Thread thread = new Thread(task);
        // 1. 线程未开始; NEW 状态;
        System.out.println("1. before start: thread.getState(): " + thread.getState());
        assertEquals(Thread.State.NEW, thread.getState());
        // 把重量锁抢了
        synchronized (task) {
    
    
            // 启动线程;
            thread.start();
            // 等待执行到要请求管程锁
            task.beforeMonitorLatch.await();
            TimeUnit.MILLISECONDS.sleep(100L);
            // 3. 线程在阻塞状态: 等待管程锁
            System.out.println("3. blocked by monitor: thread.getState(): " + thread.getState());
            assertEquals(Thread.State.BLOCKED, thread.getState());
            // 将轻量锁抢了
            task.lock.lock();
        }
        // 等待执行到要请求锁
        task.beforeLockLatch.await();
        // 稍微等一等
        TimeUnit.MILLISECONDS.sleep(100L);
        // 4. 等待状态; 此处是等待轻量锁;
        System.out.println("4. waiting lock: thread.getState(): " + thread.getState());
        assertEquals(Thread.State.WAITING, thread.getState());
        // 释放锁
        task.lock.unlock();
        // 让线程继续执行
        task.toSleepLatch.countDown();
        TimeUnit.MILLISECONDS.sleep(100);
        // 此时 thread 应该在睡眠中
        System.out.println("5.Thread in sleep: thread.getState(): " + thread.getState());
        assertEquals(Thread.State.TIMED_WAITING, thread.getState());
        // 等线程结束来汇合
        thread.join();
        System.out.println("6. after join: thread.getState(): " + thread.getState());
        assertEquals(Thread.State.TERMINATED, thread.getState());
    }

    @Override
    public void run() {
    
    
        System.out.println("=== enter run() ===");
        // 获取执行此任务的线程;
        Thread thread = Thread.currentThread();
        // 2. 线程在执行过程中; 在JVM看来属于可执行状态
        assertEquals(Thread.State.RUNNABLE, thread.getState());
        System.out.println("2. executing run: thread.getState(): " + thread.getState());

        //请求管程锁
        System.out.println("=== before synchronized (this)===");
        beforeMonitorLatch.countDown();
        synchronized (this) {
    
    
            System.out.println("===synchronized (this) enter===");
        }

        // 设置标识: 即将请求轻量锁
        beforeLockLatch.countDown();
        System.out.println("===before lock.lock()===");
        // 等待锁
        lock.lock();
        lock.unlock();

        try {
    
    
            // 等待标志: 需要睡眠
            this.toSleepLatch.await();
            // 睡眠500毫秒
            System.out.println("===before sleep()===");
            TimeUnit.MILLISECONDS.sleep(500L);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("===finish run()===");
    }

    // 工具方法; 程序断言相等
    static public void assertEquals(Object expected, Object actual) {
    
    
        if (false == Objects.equals(expected, actual)) {
    
    
            throw new RuntimeException("Not Equals: expected=" + expected + "; actual=" + actual);
        }
    }
}

Le résultat de l'exécution de la sortie de la console est :

1. before start: thread.getState(): NEW
=== enter run() ===
2. executing run: thread.getState(): RUNNABLE
=== before synchronized (this)===
3. blocked by monitor: thread.getState(): BLOCKED
===synchronized (this) enter===
===before lock.lock()===
4. waiting lock: thread.getState(): WAITING
===before sleep()===
5.Thread in sleep: thread.getState(): TIMED_WAITING
===finish run()===
6. after join: thread.getState(): TERMINATED

Liens connexes:

Pour plus d'informations, reportez-vous à :

Je suppose que tu aimes

Origine blog.csdn.net/renfufei/article/details/112339222
conseillé
Classement