JavaにおけるOS上のスレッドの取り扱い

Threadのメモリモデルを色々調べていくうちにJavaのスレッドでOSスレッドとどうやって紐付いてるんだろうと思って調べた。
JavaにおけるTheradスケジューラはOSに依存するみたいな説明がされているが、要はJVMプロセス内でOSスレッドを生成してそれをwrapしている。
知識としては知っていたけど、改めて確認してみたかったメモ

public class Main {
    public static void main(String args[]) throws Exception {
        Thread.sleep(1000 * 60 * 60);  // プロセス終了しないように止めておく
    }
}


javacしてjava Mainでjavaプロセス起動してからPID確認

root@dc4d74fd6f08:/# jps
5684 Jps
5498 Main ←ここにいる

まずはjstackでjava threadを確認

root@dc4d74fd6f08:/# jstack 5498 | grep nid
"Attach Listener" #9 daemon prio=9 os_prio=0 tid=0x00007f29a0001000 nid=0x15ba waiting on condition [0x0000000000000000]
"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f29dc0c0000 nid=0x1587 runnable [0x0000000000000000]
"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f29dc0b0800 nid=0x1586 waiting on condition [0x0000000000000000]
"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f29dc0af000 nid=0x1585 waiting on condition [0x0000000000000000]
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f29dc0ac000 nid=0x1584 waiting on condition [0x0000000000000000]
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f29dc0aa000 nid=0x1583 runnable [0x0000000000000000]
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f29dc083800 nid=0x1582 in Object.wait() [0x00007f29c5a50000]
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f29dc07f000 nid=0x1581 in Object.wait() [0x00007f29c5b51000]
"main" #1 prio=5 os_prio=0 tid=0x00007f29dc009800 nid=0x157b waiting on condition [0x00007f29e36ea000]
"VM Thread" os_prio=0 tid=0x00007f29dc077000 nid=0x1580 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f29dc01f000 nid=0x157c runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f29dc020800 nid=0x157d runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f29dc022000 nid=0x157e runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f29dc024000 nid=0x157f runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f29dc0c2800 nid=0x1588 waiting on condition

root@dc4d74fd6f08:/# jstack 5498 | grep nid | wc -l
15

jstack的には15個のスレッドがあった


次にpsコマンドでOSスレッドを確認

root@dc4d74fd6f08:/# ps -feL | grep "java Main"
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root      5498     1  5498  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5499  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5500  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5501  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5502  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5503  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5504  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5505  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5506  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5507  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5508  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5509  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5510  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5511  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5512  0   16 11:36 ?        00:00:00 java Main
root      5498     1  5562  0   16 11:38 ?        00:00:00 java Main

root@dc4d74fd6f08:/# ps -feL | grep "java Main"| wc -l
16

ps的には16個あった


psコマンド的にはjstack比べてスレッドが一個多いのは
>root 5498 1 5498 0 16 11:36 ? 00:00:00 java Main
これが大元のjava親プロセスでメインスレッドを作るときにforkして子プロセスを作ってるっぽい(PIDとLWPが同じなので)

  • 1Processは1CPUで実行される
  • なので概念的にはProcessの中にいくらThreadを作成して並行化しようが、1CPUしか使わない
  • そこでLinuxとしてはLWP(Light Weight Process)としてメモリ共有したプロセス(軽量プロセス)を生成し、Threadとして扱う
  • 昔のLinuxはLWPがなかったので、単なるThreadだったのでマルチコアで多重スレッドにはできなかった
  • 実際、上記のps -feLの結果確認するとLWPのIDが全部違うのでOSスレッド = 軽量プロセスということになっている

スッキリした


プロセスとスレッド - Linux Kernel - FreeBSDいちゃらぶ日記