我的一位开发人员朋友最近问了一个问题:在 Linux 系统上,当运行具有线程的 Java 应用程序时,这些线程在底层 Linux 操作系统中是如何显示的?
那么什么是Java线程呢?
答案1
总长DR
在Java 1.1,绿色线程是唯一使用的线程模型Java虚拟机(JVM),9至少在索拉里斯。由于绿色线程与本机线程相比有一些限制,因此后续的 Java 版本放弃了它们,转而使用本机线程。10,11。
来源:绿色线程
下图展示了如何从操作系统角度分析 Java 线程。
背景
在研究这个问题时,我遇到了这个问题与解答,标题为:Java JVM 使用 pthread 吗? 。这个问题中有一个 JVM 源代码的链接 -OpenJDK / jdk8u / jdk8u / 热点。具体来说这一段:
// Serialize thread creation if we are running with fixed stack LinuxThreads
bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
if (lock) {
os::Linux::createThread_lock()->lock_without_safepoint_check();
}
pthread_t tid;
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
pthread_attr_destroy(&attr);
在这里我们可以看到 JVM 正在使用 pthreads,又名。POSIX 线程。其他详细信息来自pthreads(7) 手册页:
POSIX.1 指定了一组用于线程编程的接口(函数、头文件),通常称为 POSIX 线程或 Pthread。单个进程可以包含多个线程,所有线程都执行同一个程序。这些线程共享相同的全局内存(数据和堆段),但每个线程都有自己的堆栈(自动变量)。
鉴于此,Java 中存在的线程只是 Linux 上的 pthread。
例子
实验为了进一步向我们自己证明这一点,我们可以使用以下示例斯卡拉应用程序归根结底,这只是一个 Java 应用程序。
该应用程序在 Docker 容器中运行,但我们可以使用它来研究正在运行的使用线程的 Java 应用程序。要使用这个应用程序,我们只需克隆 Git 存储库,然后构建并运行 Docker 容器。
构建应用程序$ git clone https://github.com/slmingol/jvmthreads.git
$ cd jvmthreads
$ docker build -t threading .
$ docker run -it -v ~/.coursier/cache:/root/.cache/coursier -v ~/.ivy2:/root/.ivy2 -v ~/.sbt:/root/.sbt -v ~/.bintray:/root/.bintray -v $(pwd):/threading threading:latest /bin/bash
此时,您应该坐在 Docker 容器内并出现以下类型的提示:
root@27c0fa503da6:/threading#
运行应用程序
从这里您将要运行该sbt
应用程序:
$ sbt compile compileCpp "runMain com.threading.ThreadingApp"
当这个应用程序开始运行时,您可以对应用程序使用Ctrl+ ,以便我们可以研究它。ZSIGSTP
root@27c0fa503da6:/threading# sbt compile compileCpp "runMain com.threading.ThreadingApp"
[info] Loading settings from metaplugins.sbt ...
[info] Loading project definition from /threading/project/project
[info] Loading settings from plugins.sbt ...
[info] Loading project definition from /threading/project
[info] Loading settings from build.sbt ...
[warn] Missing bintray credentials. Either create a credentials file with the bintrayChangeCredentials task, set the BINTRAY_USER and BINTRAY_PASS environment variables or pass bintray.user and bintray.pass properties to sbt.
[warn] Missing bintray credentials. Either create a credentials file with the bintrayChangeCredentials task, set the BINTRAY_USER and BINTRAY_PASS environment variables or pass bintray.user and bintray.pass properties to sbt.
^Z
[1]+ Stopped sbt compile compileCpp "runMain com.threading.ThreadingApp"
分析应用程序
从这里我们现在可以使用典型的 UNIX 工具,例如ps
从操作系统的角度查看被测应用程序的行为方式。
标准ps
cmd 仅显示正在运行的典型应用程序。
root@27c0fa503da6:/threading# ps -eaf
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 02:14 pts/0 00:00:00 /bin/bash
root 1503 1 0 02:37 pts/0 00:00:00 bash /usr/bin/sbt compile compileCpp runMain com.threading.ThreadingApp
root 1571 1503 98 02:37 pts/0 00:00:35 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar
root 1707 1571 0 02:37 pts/0 00:00:00 git describe --tags --abbrev=8 --match v[0-9]* --always --dirty=+20191026-0237
root 1718 1 0 02:37 pts/0 00:00:00 ps -eaf
然而,使用查看线程ps
可以看到更完整的情况:
root@27c0fa503da6:/threading# ps -eLf | head -8
UID PID PPID LWP C NLWP STIME TTY TIME CMD
root 1 0 1 0 1 02:14 pts/0 00:00:00 /bin/bash
root 1943 1 1943 0 1 03:08 pts/0 00:00:00 bash /usr/bin/sbt compile compileCpp runMain com.threading.ThreadingApp
root 2011 1943 2011 0 32 03:08 pts/0 00:00:00 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar compile compileCpp runMain com.threading.ThreadingApp
root 2011 1943 2012 0 32 03:08 pts/0 00:00:05 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar compile compileCpp runMain com.threading.ThreadingApp
root 2011 1943 2013 0 32 03:08 pts/0 00:00:00 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar compile compileCpp runMain com.threading.ThreadingApp
root 2011 1943 2014 0 32 03:08 pts/0 00:00:00 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar compile compileCpp runMain com.threading.ThreadingApp
root 2011 1943 2015 0 32 03:08 pts/0 00:00:00 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar compile compileCpp runMain com.threading.ThreadingApp
笔记:上面我们可以看到有很多线程。此视图中感兴趣的列是LWP
和NLWP
。
LWP
代表轻量级进程线程NLWP
代表 LWP 数量
NLWP 数字很重要,因为它告诉您与 PID 关联的线程总数。在我们的例子中,该数字是 32。您可以像这样确认:
root@27c0fa503da6:/threading# ps -eLf|grep -E "[3]2.*java" | wc -l
32
您还可以使用这些ps
命令来获取验证这些线程的替代方法:
root@27c0fa503da6:/threading# ps -Lo pid,lwp,pri,nice,start,stat,bsdtime,cmd,comm | head -5
PID LWP PRI NI STARTED STAT TIME CMD COMMAND
1 1 19 0 02:14:42 Ss 0:00 /bin/bash bash
1943 1943 19 0 03:08:41 T 0:00 bash /usr/bin/sbt compile c bash
2011 2011 19 0 03:08:41 Tl 0:00 java -Xms1024m -Xmx1024m -X java
2011 2012 19 0 03:08:41 Tl 0:05 java -Xms1024m -Xmx1024m -X java
注1:由于l
STAT 列上的 ,此表单显示这些是 pthread。
S
- 可中断睡眠(等待事件完成)T
- 由作业控制信号停止l
- 是多线程的(使用 CLONE_THREAD,就像 NPTL pthreads 那样)
笔记2:和在这里很重要S
,T
因为它们表明该过程是通过使用 SIGSTP 控制信号的Ctrl+停止的。Z
您还可以使用ps -T
开关将它们视为线程:
root@27c0fa503da6:/threading# ps -To pid,tid,tgid,tty,time,comm | head -5
PID TID TGID TT TIME COMMAND
1 1 1 pts/0 00:00:00 bash
1943 1943 1943 pts/0 00:00:00 bash
2011 2011 2011 pts/0 00:00:00 java
2011 2012 2011 pts/0 00:00:05 java
上面的开关ps
:
-L Show threads, possibly with LWP and NLWP columns.
-T Show threads, possibly with SPID column.
完整运行App
出于参考目的,如果您好奇的话,这里是 Scala/Java 应用程序的完整运行。
root@27c0fa503da6:/threading# sbt compile compileCpp "runMain com.threading.ThreadingApp"
[info] Loading settings from build.sbt ...
[warn] Missing bintray credentials. Either create a credentials file with the bintrayChangeCredentials task, set the BINTRAY_USER and BINTRAY_PASS environment variables or pass bintray.user and bintray.pass properties to sbt.
[warn] Missing bintray credentials. Either create a credentials file with the bintrayChangeCredentials task, set the BINTRAY_USER and BINTRAY_PASS environment variables or pass bintray.user and bintray.pass properties to sbt.
[info] Set current project to threading (in build file:/threading/)
[info] Executing in batch mode. For better performance use sbt's shell
[warn] Credentials file /root/.bintray/.credentials does not exist, ignoring it
[success] Total time: 2 s, completed Oct 26, 2019 4:11:38 AM
[success] Total time: 1 s, completed Oct 26, 2019 4:11:39 AM
[warn] Credentials file /root/.bintray/.credentials does not exist, ignoring it
[info] Running (fork) com.threading.ThreadingApp
[info] Started a linux thread 140709608359680!
[info] Started a linux thread 140709599966976!
[info] Starting thread_entry_pointStarted a linux thread 140709591574272!
[info] Starting thread_entry_pointStarting thread_entry_pointStarted a linux thread 140709583181568!
[info] Running Thread 1
[info] Starting thread_entry_pointStarted a linux thread 140709369739008!
[info] Running Thread 2
[info] Starting thread_entry_pointStarted a linux thread 140709608359680!
[info] Running Thread 3
[info] Starting thread_entry_pointStarted a linux thread 140709599966976!
[info] Running Thread 4
[info] Running Thread 5Starting thread_entry_pointStarting thread_entry_pointStarted a linux thread 140709361346304!
[info] Running Thread 6
[info] Starting thread_entry_pointStarted a linux thread 140709583181568!
[info] Started a linux thread 140709591574272!
[info] Starting thread_entry_pointStarting thread_entry_pointStarted a linux thread 140709352953600!
[info] Running Thread 7
[info] Running Thread 9
[info] Started a linux thread 140709369739008!
[info] Starting thread_entry_pointStarted a linux thread 140709608359680!
[info] Running Thread 8
[info] Starting thread_entry_pointStarted a linux thread 140709344560896!
[info] Starting thread_entry_pointStarted a linux thread 140709583181568!
[info] Starting thread_entry_pointStarted a linux thread 140709599966976!
[info] Starting thread_entry_pointStarted a linux thread 140709336168192!
[info] Running Thread 10
[info] Running Thread 11
[info] Starting thread_entry_pointStarted a linux thread 140709327775488!
[info] Running Thread 12Started a linux thread 140709591574272!
[info] Running Thread 13
[info] Running Thread 14
[info] Running Thread 16
[info] Running Thread 15
[info] Running Thread 18
[info] Running Thread 17
[info] Running Thread 19
[info] Starting thread_entry_pointStarting thread_entry_point
[success] Total time: 1 s, completed Oct 26, 2019 4:11:40 AM
线程转储?
有些人问我们如何将 Java 线程等同于 Linux LWP 联系在一起。为此,我们可以使用 Java 线程转储来比较 2。
我们再次使用上面相同的 Scala 应用程序,并且我们将Ctrl+ Z。
root@52a4b6e78711:/threading# sbt compile compileCpp "runMain com.threading.ThreadingApp"
[info] Loading settings from metaplugins.sbt ...
[info] Loading project definition from /threading/project/project
^Z
[1]+ Stopped sbt compile compileCpp "runMain com.threading.ThreadingApp"
完成此操作后,我们需要向 JVM 发送 SIGQUIT。为此,您通常可以使用kill -3 <PID of JVM>
:
root@52a4b6e78711:/threading# ps -eaf
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:36 pts/0 00:00:00 /bin/bash
root 7 1 0 12:37 pts/0 00:00:00 bash /usr/bin/sbt compile compileCpp runMain com.threading.ThreadingApp
root 75 7 99 12:37 pts/0 00:00:17 java -Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxMetaspaceSize=256m -jar /usr/share/sbt/bin/sbt-launch.jar
root 130 1 0 12:37 pts/0 00:00:00 ps -eaf
root@52a4b6e78711:/threading# kill -3 75
然后我们需要允许程序恢复fg
:
root@52a4b6e78711:/threading# fg
sbt compile compileCpp "runMain com.threading.ThreadingApp"
2019-10-26 12:38:00
Full thread dump OpenJDK 64-Bit Server VM (25.181-b13 mixed mode):
"scala-execution-context-global-32" #32 daemon prio=5 os_prio=0 tid=0x00007f87d8002800 nid=0x80 runnable [0x00007f880973d000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c1b08a68> (a scala.concurrent.impl.ExecutionContextImpl$$anon$3)
at java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1824)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1693)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
"scala-execution-context-global-33" #33 daemon prio=5 os_prio=0 tid=0x00007f87dc001000 nid=0x7f runnable [0x00007f880983e000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c1b08a68> (a scala.concurrent.impl.ExecutionContextImpl$$anon$3)
at java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1824)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1693)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
"scala-execution-context-global-31" #31 daemon prio=5 os_prio=0 tid=0x00007f87d8001000 nid=0x7e waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"scala-execution-context-global-30" #30 daemon prio=5 os_prio=0 tid=0x00007f87e4003800 nid=0x7d waiting on condition [0x00007f8809a40000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c1b08a68> (a scala.concurrent.impl.ExecutionContextImpl$$anon$3)
at java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1824)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1693)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
笔记:在上面命令的部分输出中,kill -3
您可以看到 JVM 中的线程在未经分析的情况下排列起来。其中有 32 个,这表明 Java 线程实际上与 Linux LWP 是 1:1。