Javaのスレッドの数や状態を確認するコマンド

Javaアプリケーションサーバの保守運用をしていると、現在起動しているスレッドの数や状況を知りたいということがあります。一般的なJavaアプリケーションサーバであれば、何らかのヘルスチェック機能が付属していると思いますが、状況によってはそのような機能が利用できないこともあると思います。そのような状況で、どのようにすればスレッドの数や状況を知ることができるでしょうか?

# 例

以下の`Server.java`はJavaアプリケーションサーバを模したプログラムで、3つの子スレッドをforkします。

```java:Server.java
import java.util.ArrayList;
import java.util.List;

public class Server{
    public static void main(String[] args) throws InterruptedException {
        // 3個の子スレッドをforkする。
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 3	; i++) {
            Thread thread = new Thread(() -> sleepForever());
            thread.start();
            threads.add(thread);
        }

        // 親スレッド自体をsleepさせる。
        sleepForever();
    }

    private static void sleepForever() {
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```

これをコンパイルし、実行しておきます。

```
$ java Server &
[1] 404
```

# (1) jstack

スレッドの数や状態を知りたいのであれば`jstack`がベストではないでしょうか。`jstack`はJDKに付属するコマンドなので、Javaアプリケーションサーバを使用しているシステムであれば、利用できることも多いはず。

以下は`jstack`の実行例です。`main`スレッド、その子スレッドである`Thread-0``Thread-1``Thread-2`に加え、jvmが利用するいくつかのスレッドの状態がスタックトレースとともに表示されていることがわかります。

```
$ jstack 404
2019-12-26 00:06:09
Full thread dump OpenJDK 64-Bit Server VM (11.0.4+11-post-Ubuntu-1ubuntu218.04.3 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x00007f6a280029d0, length=13, elements={
0x00007f6a74011800, 0x00007f6a74165000, 0x00007f6a74167000, 0x00007f6a7416f800,
0x00007f6a74171800, 0x00007f6a74174000, 0x00007f6a74176000, 0x00007f6a741d8800,
0x00007f6a741e2000, 0x00007f6a741f1800, 0x00007f6a741f3800, 0x00007f6a741f5000,
0x00007f6a28001000
}

"main" #1 prio=5 os_prio=0 cpu=90.00ms elapsed=496.33s tid=0x00007f6a74011800 nid=0x195 waiting on condition  [0x00007f6a7b56f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@11.0.4/Native Method)
        at Server.sleepForever(Server.java:20)
        at Server.main(Server.java:15)

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=0.00ms elapsed=496.30s tid=0x00007f6a74165000 nid=0x19c waiting on condition  [0x00007f6a58adf000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.4/Native Method)
        at java.lang.ref.Reference.processPendingReferences(java.base@11.0.4/Reference.java:241)
        at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.4/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=0 cpu=0.00ms elapsed=496.30s tid=0x00007f6a74167000 nid=0x19d in Object.wait()  [0x00007f6a589cf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(java.base@11.0.4/Native Method)
        - waiting on <0x000000074e50a2c8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
        - waiting to re-lock in wait() <0x000000074e50a2c8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:176)
        at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.4/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=496.29s tid=0x00007f6a7416f800 nid=0x19e runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 cpu=10.00ms elapsed=496.29s tid=0x00007f6a74171800 nid=0x19f waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #8 daemon prio=9 os_prio=0 cpu=30.00ms elapsed=496.29s tid=0x00007f6a74174000 nid=0x1a0 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #9 daemon prio=9 os_prio=0 cpu=10.00ms elapsed=496.29s tid=0x00007f6a74176000 nid=0x1a1 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #10 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=496.27s tid=0x00007f6a741d8800 nid=0x1a2 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #11 daemon prio=8 os_prio=0 cpu=0.00ms elapsed=496.26s tid=0x00007f6a741e2000 nid=0x1a4 in Object.wait()  [0x00007f6a17fef000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(java.base@11.0.4/Native Method)
        - waiting on <0x000000074e416e98> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
        - waiting to re-lock in wait() <0x000000074e416e98> (a java.lang.ref.ReferenceQueue$Lock)
        at jdk.internal.ref.CleanerImpl.run(java.base@11.0.4/CleanerImpl.java:148)
        at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)
        at jdk.internal.misc.InnocuousThread.run(java.base@11.0.4/InnocuousThread.java:134)

"Thread-0" #12 prio=5 os_prio=0 cpu=0.00ms elapsed=496.25s tid=0x00007f6a741f1800 nid=0x1a5 waiting on condition  [0x00007f6a17a9f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@11.0.4/Native Method)
        at Server.sleepForever(Server.java:20)
        at Server.lambda$0(Server.java:9)
        at Server$$Lambda$1/0x0000000840060840.run(Unknown Source)
        at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)

"Thread-1" #13 prio=5 os_prio=0 cpu=0.00ms elapsed=496.25s tid=0x00007f6a741f3800 nid=0x1a6 waiting on condition  [0x00007f6a1798f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@11.0.4/Native Method)
        at Server.sleepForever(Server.java:20)
        at Server.lambda$0(Server.java:9)
        at Server$$Lambda$1/0x0000000840060840.run(Unknown Source)
        at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)

"Thread-2" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=496.25s tid=0x00007f6a741f5000 nid=0x1a7 waiting on condition  [0x00007f6a1787f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@11.0.4/Native Method)
        at Server.sleepForever(Server.java:20)
        at Server.lambda$0(Server.java:9)
        at Server$$Lambda$1/0x0000000840060840.run(Unknown Source)
        at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)

"Attach Listener" #15 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=0.10s tid=0x00007f6a28001000 nid=0x1dc waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=0 cpu=0.00ms elapsed=496.31s tid=0x00007f6a74162000 nid=0x19b runnable

"GC Thread#0" os_prio=0 cpu=0.00ms elapsed=496.32s tid=0x00007f6a74028000 nid=0x196 runnable

"G1 Main Marker" os_prio=0 cpu=0.00ms elapsed=496.32s tid=0x00007f6a74070800 nid=0x197 runnable

"G1 Conc#0" os_prio=0 cpu=0.00ms elapsed=496.32s tid=0x00007f6a74072800 nid=0x198 runnable

"G1 Refine#0" os_prio=0 cpu=0.00ms elapsed=496.32s tid=0x00007f6a74138000 nid=0x199 runnable

"G1 Young RemSet Sampling" os_prio=0 cpu=0.00ms elapsed=496.32s tid=0x00007f6a74139800 nid=0x19a runnable
"VM Periodic Task Thread" os_prio=0 cpu=0.00ms elapsed=496.27s tid=0x00007f6a741db000 nid=0x1a3 waiting on condition

JNI global refs: 6, weak refs: 0
```

# (2) ps

Java on LinuxはスレッディングをOSのネィティブスレッドに委譲します。つまり「Javaのスレッド」=「OSのスレッド」という関係にあります。したがって、OSのスレッドの状態を確認できるコマンドを利用すれば、間接的にJavaのスレッドの状態も確認できるというわけです。

OSのスレッドの状態を確認する方法はいくつかありますが、とりわけ汎用的で便利なのは`ps`でしょう。実行例は以下の通りです。スレッド名やスタックトレースなど、`jstack`に比べると情報量は少なくなりますが、簡単な確認程度であれば、この程度でも十分でしょう。

```
$ ps -L mu 404
USER       PID   LWP %CPU NLWP %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
dev        404     -  0.0   21  0.2 6100336 25820 tty1    -    Dec25   0:00 java Server
dev          -   404  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   405  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   406  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   407  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   408  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   409  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   410  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   411  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   412  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   413  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   414  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   415  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   416  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   417  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   418  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   419  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   420  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   421  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   422  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   423  0.0    -    -      -     - -        Sl   Dec25   0:00 -
dev          -   476  0.0    -    -      -     - -        Sl   00:06   0:00 -
```

# 参考

この記事は次の環境で稼働確認を行いました。

| software | version |
|:-:|:--|
| OS | Ubuntu 18.04.2 LTS (Windows Subsystem for Linux) |
| JDK | openjdk 11.0.4 2019-07-16 |
| ps | ps from procps-ng 3.3.12 |