Java スレッド ダンプを取得する一般的な方法

1. スレッドダンプの概要

スレッド ダンプ (スレッド ダンプ) は、JVM 内のすべてのスレッド状態情報のスナップショットです。

スレッド ダンプは通常、テキスト形式を使用します。テキスト ファイルに保存して、手動で表示して分析することも、ツール/API を使用して自動的に分析することもできます。

Java のスレッド モデルは、オペレーティング システムのスレッド スケジューリング モデルを直接使用し、単純なカプセル化のみを実行します。

スレッド呼び出しスタック。メソッド呼び出しスタックとも呼ばれます。たとえば、プログラムの実行プロセスでは、「obj1.method2呼ばれるobj2.methodBobj2.methodBと「再び呼び出される」という一連のメソッド呼び出しチェーンが発生しますobj3.methodC各スレッドの状態は、この呼び出しスタックによって表すことができます。

スレッド ダンプは個々のスレッドの動作を示し、問題の診断とトラブルシューティングに非常に役立ちます。

以下では、具体的な例を使用して、Java スレッド ダンプを取得するためのさまざまなツールとその使用方法を示します。

2. JDK に付属のツールを使用する

通常、Java アプリケーションのスレッド ダンプを取得するには、JDK に付属のコマンド ライン ツールを使用します。これらのツールは、JDK メイン ディレクトリの bin フォルダーにあります。

したがって、PATH パスを設定するだけです。設定方法がわからない場合は、JDK環境の準備を参照してください。

2.1 jstackツール

jstack は JDK の組み込みコマンド ライン ツールで、特にスレッドのステータスを表示するために使用され、スレッド ダンプの実行にも使用できます。

jps一般に、まずまたはコマンドを使用して Java プロセスに対応する pid を検索しps、次にその pid を使用してコンソールにスレッド ダンプを出力します。もちろん、出力をファイルにリダイレクトすることもできます。

jstack ツールを使用してスレッド ダンプを取得するための基本的なパラメーターの形式は次のとおりです。

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

以下の具体的なデモをご覧ください。

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

出力は次のようなものになります。

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

対応するパラメータ オプションはオプションです。具体的な意味は以下の通りです。

  • -Fスレッド ダンプを強制するオプション。jstack pidフリーズする場合があるため、-Fフラグを追加できます
  • -lオプションでは、ヒープ メモリ内に所有されているシンクロナイザーとリソース ロックが検索されます。
  • -mオプションで、ネイティブ スタック フレーム (C および C++) を追加出力します。

たとえば、スレッド ダンプを取得して結果をファイルに出力するには、次のようにします。

jstack -F 17264 > /tmp/threaddump.txt

コマンドを使用してjps、ローカル Java プロセスの pid を取得します。

2.2 Java ミッション コントロール

Java Mission Control (JMC) は、Java アプリケーションのさまざまなデータを収集および分析するためのクライアント グラフィカル インターフェイス ツールです。
JMC を起動すると、まずローカル コンピュータで実行されている Java プロセスのリストが表示されます。もちろん、JMC を介してリモート Java プロセスに接続することもできます。

対応するプロセスを右クリックし、「Start Flight Recording (フライト記録の開始)」を選択できます。完了すると、「スレッド」タブに「スレッド ダンプ」が表示されます。

ここに画像の説明を挿入

2.3 jvisualvm

jvisualvm は、シンプルで実用的なクライアント グラフィカル インターフェイス ツールで、Java アプリケーションの監視、トラブルシューティング、JVM のパフォーマンス分析に使用できます。

スレッド ダンプの取得にも使用できます。Java プロセスを右クリックし、[スレッド ダンプ] オプションを選択すると、スレッド ダンプを作成できます。完了すると、新しいタブで自動的に開きます。

ここに画像の説明を挿入

2.4 jcmd

jcmd ツールは基本的に、一連のコマンドをターゲット JVM に送信します。多くの機能がサポートされていますが、リモート JVM への接続はサポートされていません。Java プロセスのローカル マシンでのみ使用できます。

コマンドの 1 つはThread.printスレッド ダンプを取得することであり、使用例は次のとおりです。

jcmd 17264 Thread.print

2.5コンソール

jconsole ツールでは、スレッド スタック トレースを表示することもできます。
jconsole を開いて実行中の Java プロセスに接続し、「スレッド」タブに移動すると、各スレッドのスタック トレースを表示できます。

ここに画像の説明を挿入

2.6 概要

JDK の多くのツールを使用してスレッド ダンプを取得できることがわかりました。それらの長所と短所を要約してまとめてみましょう。

  • jstack: スレッド ダンプを取得するための最も簡単で便利なツール。Java 8 以降では、代わりに jcmd ツールを使用できます。
  • jmc: 強化された JDK パフォーマンス分析および問題診断ツール。このツールを使用したプロファイリングのオーバーヘッドは非常に低いです。
  • jvisualvm: 優れたグラフィカル インターフェイスを備えた軽量のオープン ソース分析ツールで、さまざまな強力な機能プラグインをサポートしています。
  • jcmd: 非常に強力なネイティブ ツールで、Java 8 以降をサポートします。スレッド ダンプのキャプチャ (jstack)、ヒープ ダンプ (jmap)、システム プロパティの表示、コマンド ライン パラメータの表示 (jinfo) など、複数のツールの機能を統合します。
  • jconsole: スレッド スタック トレース情報の表示にも使用できます。

3. Linuxコマンドの使用

エンタープライズ アプリケーション サーバーでは、セキュリティ上の理由から JRE のみがインストールされる場合があります。現時点では、これらの JDK 組み込みツールは使用できません。
ただし、スレッド ダンプを取得する方法はまだあります。

3.1kill -3コマンドの使用方法

Unix/Linux などのシステムでは、killコマンドを使用してスレッド ダンプを取得できます。基本的な実装原理は、kill()システム コールを通じてシグナル パラメーターをプロセスに送信することです。ここで送信する必要があるのは-3信号です。

一般に、まず を介してjpsJAVA プロセスに対応する pid を見つけます。kill -3使用例は次のとおりです。

kill -3 17264

3.2 Ctrl + Break(Windows)

Windows オペレーティング システムのコマンド ライン ウィンドウでは、キーの組み合わせを使用してCtrl + Breakスレッド ダンプを取得できます。もちろん、最初に Java プログラムが起動されたコンソール ウィンドウに移動し、次にCTRLキーとBreakキーを同時に押す必要があります。

一部のキーボードには「 」キーがないことに注意してくださいBreak
この場合、CTRLSHIFTPauseキーを組み合わせて使用​​できます。

どちらのコマンドでも、スレッド ダンプをコンソールに出力できます。

4. ThreadMxBean をプログラムで使用する

JMX テクノロジーは、さまざまな高度な操作をサポートします。ThreadMxBeanスレッドダンプは で実行できます。

サンプルコードは次のとおりです。

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();
}

上記のコードの動作は非常に単純で、まず を介してオブジェクトをManagementFactory取得します。メソッドのブール値パラメーターと は、保持されているシンクロナイザーとモニターのロックをエクスポートするかどうかを示します。ThreadMxBean
lockedMonitorslockedSynchronizers

ただし、このアプローチにはいくつかの欠点があります。

    1. パフォーマンスはあまり良くなく、大量のリソースを消費します。
    1. threadDump.toString()このメソッドは最大 8 つのスタック フレーム ( MAX_FRAMES = 8) のみを出力します。toString コードをコピーして、自分で変更/フィルター処理することができます。
    1. ネイティブ スレッド (GC スレッドなど) はダンプされません。

代替案:

    1. ランタイムを通じて jstack を呼び出してスレッド ダンプ情報を取得します。失敗した場合は、JMX モードに戻ります。

コードの一部:


    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. まとめ

スレッドダンプを取得するさまざまな方法を具体的な例とともに示しました。

最初にさまざまな JDK 組み込みツールを紹介し、
次にコマンド ライン方法について説明し、
最後に JMX プログラミングの方法を紹介します。

完全なサンプル コードについては、GitHub リポジトリを参照してください。

6. 付録: スレッドのステータスとサンプルコード

以下を含むスレッドのステータスを参照できますThread.State

  • NEW: 未開始; たとえば、start メソッドがまだ実行されていません (終了しています)。
  • RUNNABLE: 実行可能なステータス。これは JVM の視点であり、CPU が使用されているかどうかはオペレーティング システムのスケジューリングによって決まります。
  • BLOCKED: ブロック状態 (同期メソッド/同期ブロックへの入力、ロック リソースの待機など)。
  • WAITING:などのロック リソースを待機していますUnsafe.park()Object.wait()
  • TIMED_WAITINGUnsafe.park():などObject.wait()ロック リソースを待機する時間制限付き。
  • TERMINATED: 終了しました。スレッドのタスクの実行が終了しました。

テストコード:


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);
        }
    }
}

コンソール出力の実行結果は次のとおりです。

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

関連リンク:

詳細については、以下を参照してください。

おすすめ

転載: blog.csdn.net/renfufei/article/details/112339222