目录
-
背景
-
jstack 是什么?
-
使用 jstack 查看 Spring Boot 启动时线程
-
-
【2】Druid 数据库连接线程
-
【1】MySQL Statement Cancellation Timer 线程
-
【6】RMI 相关线程
-
【1】DestroyJavaVM 线程
-
【14】端口 8080 的 HTTP 相关线程
-
【1】调度线程
-
【14】端口8293 的 HTTP 相关线程
-
【2】container 线程
-
【3】Catalina-utility 线程
-
【1】Log4j 日志线程
-
【1】Service 线程
-
【4】编译器线程
-
【1】中断线程
-
【1】Attach 线程
-
【1】信号分派线程
-
【1】Finalizer 线程
-
【1】Reference Handler 线程
-
【1】VM 线程
-
【8】垃圾回收线程
-
【1】VM 定期任务线程
-
JNI global references
-
-
小结
背景
最近学习 Java 线程相关知识,接触到了
jstack
这个命令,挺有意思的。下面动手试试。
jstack 是什么?
简单来说就是,把 java 当前各个线程相关状态打印出来。
实际使用时,首先借助
jps
命令找到希望观察的 java 进程 ID,然后执行
jstack
+ 查出来的进程 ID 来查看该进程的线程状态。
使用 jstack 查看 Spring Boot 启动时线程
手头正好有一个公司的 Spring Boot 项目,把它启动了,然后执行
jps
命令:
$ jps
1992 **Application // 这个就是 Spring Boot 应用的名称(打了星号)
2792
8488 Jps
12028 RemoteMavenServer36
13164
3676 Launcher
既然知道了 Spring Boot 应用的进程 ID,接下来对该进程执行
jstack
命令:
$ jstack 1992
2021-07-24 14:10:34
Full thread dump OpenJDK 64-Bit Server VM (25.292-b10 mixed mode):
// 输出内容很长,下面分组研究
【2】Druid 数据库连接线程
在打印内容中可以看到,第一部分是两个 Druid 数据库连接线程:
"Druid-ConnectionPool-Destroy-566039179" #88 daemon prio=5 os_prio=0 tid=0x000001f19c6e9000 nid=0x2e74 waiting on condition [0x000000861c6fe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource.java:2540)
"Druid-ConnectionPool-Create-566039179" #87 daemon prio=5 os_prio=0 tid=0x000001f19c6ee000 nid=0x1828 waiting on condition [0x000000861c5ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077a9afe10> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2443)
【1】MySQL Statement Cancellation Timer 线程
接着是一个相当奇怪的“MySQL 语句取消计时器”:
"MySQL Statement Cancellation Timer" #86 daemon prio=5 os_prio=0 tid=0x000001f19c6e5000 nid=0x778 in Object.wait() [0x000000861c4ff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076d266448> (a java.util.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at java.util.TimerThread.mainLoop(Timer.java:526)
- locked <0x000000076d266448> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)
【6】RMI 相关线程
查了一下,
RMI
是
Java Remote Method Invocation
,相当于 Java 内置的 RPC 吧。
【5】RMI TCP 连接线程
然后是 5 个
RMI TCP
连接线程,中间隔了很远。
但是我不太明白,只是启动一个 Spring Boot,怎么就涉及 RMI 了?
"RMI TCP Connection(7)-172.27.80.1" #83 daemon prio=5 os_prio=0 tid=0x000001f19c6eb800 nid=0x1008 runnable [0x000000861c2fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
"RMI TCP Connection(6)-172.27.80.1" #80 daemon prio=5 os_prio=0 tid=0x000001f19c6ec800 nid=0x3e38 runnable [0x000000861c1fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
# 中间隔了很多很多别的线程
"RMI TCP Connection(5)-172.27.80.1" #30 daemon prio=5 os_prio=0 tid=0x000001f19938a800 nid=0x1df4 runnable [0x0000008619afd000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
"RMI TCP Connection(4)-172.27.80.1" #28 daemon prio=5 os_prio=0 tid=0x000001f1997f1800 nid=0x32dc runnable [0x00000086199fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
"RMI TCP Connection(2)-172.27.80.1" #16 daemon prio=5 os_prio=0 tid=0x000001f19a009000 nid=0x11d8 runnable [0x0000008619bfe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
还有,这里的
172.27.80.1
是谁?
查了一下,是
Ethernet adapter vEthernet (Default Switch)
:
Ethernet adapter vEthernet (Default Switch):
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::c97e:8317:192b:14ae%54
IPv4 Address. . . . . . . . . . . : 172.27.80.1
Subnet Mask . . . . . . . . . . . : 255.255.240.0
Default Gateway . . . . . . . . . :
【1】RMI 调度线程
在很靠后的位置有个
RMI
调度线程:
"RMI Scheduler(0)" #17 daemon prio=5 os_prio=0 tid=0x000001f19a014000 nid=0x6f0 waiting on condition [0x0000008619cfe000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c2939c20> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
# 中间略
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
【1】RMI 接受线程
最后面有个
RMI
接受线程:
"RMI TCP Accept-0" #13 daemon prio=5 os_prio=0 tid=0x000001f199965800 nid=0x287c runnable [0x00000086197ff000]
java.lang.Thread.State: RUNNABLE
at java.net.DualStackPlainSocketImpl.accept0(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
- locked <0x00000006c293a3a8> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:560)
at java.net.ServerSocket.accept(ServerSocket.java:528)
at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(LocalRMIServerSocketFactory.java:52)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377)
at java.lang.Thread.run(Thread.java:748)
【1】DestroyJavaVM 线程
然后是一个
DestroyJavaVM
线程。
简单来说,这个线程什么都不干,就是等着其他非守护线程的活儿都干完了(
join
),就关闭 Java 虚拟机。
参考链接:
DestroyJavaVM thread ALWAYS running
"DestroyJavaVM" #75 prio=5 os_prio=0 tid=0x000001f19c6e6000 nid=0x3d08 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
【14】端口 8080 的 HTTP 相关线程
接下来是 HTTP 相关的三类线程。
8080
端口是负责向用户提供服务的。
参考链接:
Tomcat NIO线程模型深入分析
【1】HTTP NIO Acceptor 线程
Acceptor 线程比较好理解,应该是专门负责接受请求:
"http-nio-8080-Acceptor-0" #73 daemon prio=5 os_prio=0 tid=0x000001f19c6e8800 nid=0x3a28 runnable [0x000000861c0fe000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:421)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:249)
- locked <0x000000077a5c2618> (a java.lang.Object)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:448)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:70)
at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
at java.lang.Thread.run(Thread.java:748)
【2】HTTP NIO ClientPoller 线程
接下来是两个 poller 线程。
简单来说就是负责观察,看前面被 acceptor 线程接受请求后创建的 channel,哪个可读/可写了,就派执行线程去处理。
前一阵刚学到,这叫 IO 多路复用模型,但我总是愿意简单理解为“事件驱动”。简单来说就是,有事儿的时候再干活儿,没事儿的时候歇着。也不是说这种模型多合理,这太正常了,是之前的模型太不合理了,没事儿的时候也要阻塞。
"http-nio-8080-ClientPoller-1" #72 daemon prio=5 os_prio=0 tid=0x000001f19c6e4800 nid=0x1f4c runnable [0x000000861bffe000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
"http-nio-8080-ClientPoller-0" #71 daemon prio=5 os_prio=0 tid=0x000001f19c4dc800 nid=0x293c runnable [0x000000861befe000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
# 中间略
at java.lang.Thread.run(Thread.java:748)
【10】HTTP NIO 执行线程
这就是真正干活儿的线程了,一共 10 个,可以理解为一个线程池。
这里省去中间的,放上首尾两个线程:
"http-nio-8080-exec-10" #70 daemon prio=5 os_prio=0 tid=0x000001f19c4db800 nid=0x1140 waiting on condition [0x000000861bdff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077a584018> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:107)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
# 中间略
"http-nio-8080-exec-1" #61 daemon prio=5 os_prio=0 tid=0x000001f19c4d0000 nid=0x10f8 waiting on condition [0x000000861b4ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077a584018> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:107)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
【1】BlockPoller 线程
说实话,我没太明白这个跟上面的 ClientPoller 有啥区别:
"NioBlockingSelector.BlockPoller-2" #60 daemon prio=5 os_prio=0 tid=0x000001f19c4d3000 nid=0x219c runnable [0x000000861b3fe000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:314)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:293)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:174)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x000000077a586688> (a sun.nio.ch.Util$3)
- locked <0x000000077a586678> (a java.util.Collections$UnmodifiableSet)
- locked <0x000000077a586508> (a sun.nio.ch.WindowsSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at org.apache.tomcat.util.net.NioBlockingSelector$BlockPoller.run(NioBlockingSelector.java:304)
【1】调度线程
调度线程只有一个,这很合理。应该就是它在决定哪些线程获得当前的 CPU 时间了吧!
注意,这是目前第一个“非守护线程”,没有标注
daemon
。
"scheduling-1" #59 prio=5 os_prio=0 tid=0x000001f19c4d7000 nid=0x38b8 waiting on condition [0x000000861b2ff000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077ad451e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
【14】端口8293 的 HTTP 相关线程
这个接口是用来提供内部管理服务的。在
application.yml
中:
management:
server:
port: 8293
这 14 个线程和上面
8080
端口的 14 个线程类似,这里就不展开了。
【2】container 线程
接下来是一个
container
线程,然后隔了几个其他线程,又来了另一个
container
线程。
注意,它们也是非守护线程。干什么用的目前还没搞懂,应该是和
tomcat
有关。
"container-1" #43 prio=5 os_prio=0 tid=0x000001f19c3a1800 nid=0x1104 waiting on condition [0x000000861a3ff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:568)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:181)
# 中间掺杂了其他线程
"container-0" #34 prio=5 os_prio=0 tid=0x000001f19a843800 nid=0x1b20 waiting on condition [0x000000861a0ff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:568)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer$1.run(TomcatWebServer.java:181)
【3】Catalina-utility 线程
然后是 4 个 Catalina-utility 线程。没挨在一起不说,奇怪的是,有两个 1 号、两个 2 号。
这个也不是守护进程,目前还没搞清楚是干嘛的。
"Catalina-utility-2" #42 prio=1 os_prio=-2 tid=0x000001f19c3a5800 nid=0x600 waiting on condition [0x000000861a2fe000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077ac6aef0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
# 中间略
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"Catalina-utility-1" #41 prio=1 os_prio=-2 tid=0x000001f19c3a0800 nid=0x2bc4 waiting on condition [0x000000861a1ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077ac6aef0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
# 中间略
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
(中间夹杂了别的线程)
"Catalina-utility-2" #33 prio=1 os_prio=-2 tid=0x000001f19a843000 nid=0x367c waiting on condition [0x0000008619fff000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077a5e42c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
# 中间略
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
"Catalina-utility-1" #32 prio=1 os_prio=-2 tid=0x000001f19a5ce000 nid=0x270c waiting on condition [0x0000008619efe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000077a5e42c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
# 中间略
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
【1】Log4j 日志线程
接着看到了熟悉的
Log4j
线程!
之前 Debug 的时候就注意到过,
Log4j
打日志不是同步的,有的时候打印日志的语句过去好几条都没打,然后又一下子打出来好几条。。
原来是用了一个单独的线程,异步来打印日志呀!这下明白了。
这个是守护线程,非常合理,就是来帮忙的嘛:
"Log4j2-TF-6-AsyncLoggerConfig-2" #19 daemon prio=5 os_prio=0 tid=0x000001f19a2a5000 nid=0x26e4 waiting on condition [0x0000008619dff000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c27729a8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at com.lmax.disruptor.TimeoutBlockingWaitStrategy.waitFor(TimeoutBlockingWaitStrategy.java:38)
at com.lmax.disruptor.ProcessingSequenceBarrier.waitFor(ProcessingSequenceBarrier.java:56)
at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:159)
at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
at java.lang.Thread.run(Thread.java:748)
【1】Service 线程
暂时没搞明白,总之是个守护线程:
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000001f1996e1800 nid=0x270 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
【4】编译器线程
看来这就是大名鼎鼎的 JIT 编译器了!编译器线程会在应用运行的过程中,把用到多的部分编译为机器码,以提高性能。
我用的是
corretto-1.8
JRE,编译器线程的数量可能和这个有关系吧:
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000001f199667800 nid=0xc94 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000001f199655800 nid=0x29b8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000001f199655000 nid=0x3c10 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000001f199653800 nid=0x1f0c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
【1】中断线程
什么。。这也是个线程??专门用来监听
Ctrl C
吗?。。
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000001f1995d9800 nid=0x21f4 runnable [0x00000086191fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x00000006c29705c0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x00000006c29705c0> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:48)
【1】Attach 线程
似乎是帮助 Debug 用的一个守护线程:
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001f1972d8800 nid=0x30d0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
参考链接:
Understanding JVM’s “Attach Listener” thread
【1】信号分派线程
按字面理解,分派信号的一个守护线程:
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001f197295000 nid=0x41c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
【1】Finalizer 线程
这个应该是垃圾回收时,专门用来调用被回收对象的
finalize
方法的守护线程:
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001f197276800 nid=0x12d8 in Object.wait() [0x0000008618eff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000006c2972a20> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000006c2972a20> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
【1】Reference Handler 线程
暂时不知道是干嘛的,推测和垃圾回收有关:
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001f1fc0d5000 nid=0xf08 in Object.wait() [0x0000008618dff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000006c2985e78> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000006c2985e78> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
【1】VM 线程
似乎也和垃圾回收等操作有很大关系,而且
jstack
命令很可能也是借助这个线程实现的。奇怪的是,这个居然不是守护线程:
"VM Thread" os_prio=2 tid=0x000001f197250000 nid=0x60 runnable
参考链接:
What does java “VM thread” do?
【8】垃圾回收线程
顾名思义,用来进行垃圾回收的线程(居然不是守护线程??):
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001f1fc071800 nid=0x29d8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001f1fc073000 nid=0x19f0 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001f1fc074800 nid=0x3f4c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001f1fc075800 nid=0x39b0 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001f1fc079000 nid=0x2194 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001f1fc07a000 nid=0x3bc runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001f1fc07d000 nid=0x8ec runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001f1fc07e000 nid=0x3d20 runnable
【1】VM 定期任务线程
似乎是用来对 JVM 进行采样和性能分析的:
"VM Periodic Task Thread" os_prio=2 tid=0x000001f199969800 nid=0x1044 waiting on condition
参考链接:
What is the “VM Periodic Task Thread”?
JNI global references
在输出的最后,还有这样一行:
JNI global references: 1504
虽然不是一个线程吧。
小结
没想到简单跑一个 Spring Boot 应用,就出来这么多线程!
之后有时间的话可以好好研究一下,分分类,继续更新吧。