从 Linux 操作系统的角度来看,Java/JVM 使用什么类型的线程?

从 Linux 操作系统的角度来看,Java/JVM 使用什么类型的线程?

我的一位开发人员朋友最近问了一个问题:在 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从操作系统的角度查看被测应用程序的行为方式。

标准pscmd 仅显示正在运行的典型应用程序。

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

笔记:上面我们可以看到有很多线程。此视图中感兴趣的列是LWPNLWP

  • 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:由于lSTAT 列上的 ,此表单显示这些是 pthread。

  • S- 可中断睡眠(等待事件完成)
  • T- 由作业控制信号停止
  • l- 是多线程的(使用 CLONE_THREAD,就像 NPTL pthreads 那样)

笔记2:和在这里很重要ST因为它们表明该过程是通过使用 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。

参考

相关内容