深入jvm-2

4 minute read

hello world程序会启动几个线程?

  mkdir hello-world
  cd hello-world
  gradle init --type java-application
  cat src/main/java/App.java 
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
public class App {
    public String getGreeting() {
        return "Hello world.";
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new App().getGreeting());

        while (true) {
            // hang here for the purpose of debug
        }
    }
}

基础环境

# java -version

java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

# gradle -v
------------------------------------------------------------
Gradle 4.8
------------------------------------------------------------

Build time:   2018-06-04 10:39:58 UTC
Revision:     9e1261240e412cbf61a5e3a5ab734f232b2f887d

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.11 compiled on March 23 2018
JVM:          1.8.0_161 (Oracle Corporation 25.161-b12)
OS:           Linux 4.9.0-6-amd64 amd64

编译/执行

gradle run

jstack结果

# jstack ${pid}

2018-07-22 11:33:53
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):

"Attach Listener" #9 daemon prio=9 os_prio=0 tid=0x00007f8620001000 nid=0x1d8e waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x00007f865c0c9800 nid=0x1d5d runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f865c0be800 nid=0x1d5c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f865c0bd000 nid=0x1d5b waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f865c0ba000 nid=0x1d5a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f865c0b8800 nid=0x1d59 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f865c085800 nid=0x1d58 in Object.wait() [0x00007f86472f1000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076df88ec0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
	- locked <0x000000076df88ec0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f865c081000 nid=0x1d57 in Object.wait() [0x00007f86473f2000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076df86b68> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x000000076df86b68> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x00007f865c00b800 nid=0x1d4f runnable [0x00007f8664c3c000]
   java.lang.Thread.State: RUNNABLE
	at App.main(App.java:12)

"VM Thread" os_prio=0 tid=0x00007f865c079800 nid=0x1d56 runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f865c021000 nid=0x1d52 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f865c023000 nid=0x1d53 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f865c024800 nid=0x1d54 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f865c026800 nid=0x1d55 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f865c0ce800 nid=0x1d5e waiting on condition 

JNI global references: 6

pstree结果

# pstree -p ${pid}
java---15*[{java}]

pidstat结果

# pidstat -t -p ${pid}
Linux 4.9.0-6-amd64 (concurrent.work) 	2018年07月22日 	_x86_64_	(4 CPU)

11时36分57秒   UID      TGID       TID    %usr %system  %guest   %wait    %CPU   CPU  Command
11时36分57秒  1000      7501         -   29.80    0.01    0.00    0.00   29.81     1  java
11时36分57秒  1000         -      7501    0.00    0.00    0.00    0.00    0.00     1  |__java
11时36分57秒  1000         -      7503   29.80    0.01    0.00    0.01   29.80     0  |__java
11时36分57秒  1000         -      7506    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7507    0.00    0.00    0.00    0.00    0.00     1  |__java
11时36分57秒  1000         -      7508    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7509    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7510    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7511    0.00    0.00    0.00    0.00    0.00     0  |__java
11时36分57秒  1000         -      7512    0.00    0.00    0.00    0.00    0.00     0  |__java
11时36分57秒  1000         -      7513    0.00    0.00    0.00    0.00    0.00     0  |__java
11时36分57秒  1000         -      7514    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7515    0.00    0.00    0.00    0.00    0.00     3  |__java
11时36分57秒  1000         -      7516    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7517    0.00    0.00    0.00    0.00    0.00     2  |__java
11时36分57秒  1000         -      7518    0.00    0.00    0.00    0.00    0.01     2  |__java
11时36分57秒  1000         -      7566    0.00    0.00    0.00    0.00    0.00     2  |__java

top结果

# top -H -p ${pid}

top - 11:34:24 up 9 min,  1 user,  load average: 1.83, 2.11, 1.20
Threads:  16 total,   1 running,  15 sleeping,   0 stopped,   0 zombie
%Cpu(s):  9.4 us,  1.9 sy,  0.0 ni, 65.8 id, 22.8 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 16129972 total, 11827668 free,  1883300 used,  2419004 buff/cache
KiB Swap:        0 total,        0 free,        0 used. 13757492 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                             
 7503 qudongf+  20   0 6554144  25896  17048 R 99.9  0.2   1:00.37 java                                                                
 7501 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7506 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7507 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7508 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7509 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7510 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7511 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7512 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7513 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7514 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7515 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7516 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7517 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7518 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java                                                                
 7566 qudongf+  20   0 6554144  25896  17048 S  0.0  0.2   0:00.00 java 

对jstack结果解释

线程名 daemon native 用途 个数 备注  
main N N 主线程 1   用户主线程
C1 CompilerThread* Y Y C1编译(解释器) 1 平台相关且与启动参数有关  
C2 CompilerThread* Y Y C2编译(JIT Compiler) 2 平台相关且与启动参数有关  
GC task thread#* Y Y GC 4 平台相关且与启动参数有关  
Finalizer Y N Finalizer处理 1 *  
Reference Handler Y N 引用处理 1 *  
VM Thread Y Y 执行VM级别(需要safe-point的)操作,如响应STW的场景(如fullgc,stackdump) 1 jvm第一个线程  
VM Periodic Task Thread Y   WatcherThread的单例,模拟时钟中断 1 *  
Signal Dispatcher Y Y 处理系统信号并分发给java信号处理函数 1 *  
Attach Listener Y Y client请求服务线程,处理jdk支持的AttachOperation 1 支持延迟加载,取决于平台和启动参数  
Service Thread Y Y 低内存探测/JVMTI事件转发(非JavaThread不能直接发) 1 java7及以下名称“Low Memory Detector”  
共计 * * * 15    

总结:hello world程序启动的线程分以下几类:

  1. 用户线程,如main
  2. GC相关线程,包括C1/C2;与运行机器配置、操作系统、启动参数相关
  3. 编译器/解释器相关线程;与运行机器配置、操作系统、启动参数相关
  4. 引用及Finalizer处理线程
  5. JVM其他后台线程
    1. VM Thread:可以说它类似linux的init进程
    2. VM Periodic Task Thread:JVM时钟模拟线程
    3. Signal Dispatcher:JVM信号处理线程
    4. Attach Listener:jdk工具类请求响应处理线程
    5. Service Thread:低内存检测、JVMTI事件转发线程

Ref

  1. http://man7.org/linux/man-pages/man1/pstree.1.html
  2. http://man7.org/linux/man-pages/man1/pidstat.1.html
  3. http://man7.org/linux/man-pages/man1/top.1.html
  4. http://blog.jamesdbloom.com/JVMInternals.html
  5. http://jakubstransky.com/2017/12/19/hotspot-jvm-internal-threads/
  6. http://openjdk.java.net/groups/hotspot/docs/RuntimeOverview.html#Thread%20Management%7Coutline
  7. http://hg.openjdk.java.net/jdk8u/jdk8u-dev/hotspot/file/d3cc20285653/src/share/vm/runtime/os.hpp#l439
  8. http://www.oracle.com/technetwork/java/jdk50-ts-guide-149808.pdf
  9. http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/cadea780bc76/src/share/classes/java/lang/ref/Finalizer.java
  10. http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/cadea780bc76/src/share/classes/java/lang/ref/Reference.java
  11. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/share/vm/runtime/vmThread.cpp
  12. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/share/vm/runtime/os.cpp#l250
  13. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/share/vm/services/attachListener.hpp#l33
  14. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/os/linux/vm/attachListener_linux.cpp#l42
  15. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/share/vm/runtime/serviceThread.hpp#l30
  16. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/1c0a59cee0e4/src/share/vm/runtime/serviceThread.cpp#l44