哈喽,大家好,欢迎阅读闲话Java的第二期:《扒一扒那些在后台默默付出的-线程》。本期由闲话哥带您深入Java进程的内部,看一看到底有哪些默默付出的线程!
用户线程和守护线程
要知道有哪些默默付出的线程,先要对线程进行一个简单的分类。线程从大类上可以分为两类,分别是用户线程和守护线程;通过Thread.setDaemon(false)设置为用户线程,通过Thread.setDaemon(true)设置为守护线程。如果不设置此属性,默认为用户线程。
用户线程和守护线程最大的区别就是,如果没有正在运行中的用户线程,即便守护线程在执行,程序也会强制中断守护线程并退出程序。
从创建线程的角色上来看,也可以分为两类,分别是自定义线程和预定义(系统)线程。这个非常好理解,我们自己创建的线程称为自定义线程,系统默认启动的线程为预定义线程。
划重点:我们本篇要扒的就是预定义(系统)线程,也就是用户预定义线程和守护预定义线程。
结论
文章比较长,先说结论。那么到底有哪些默默付出的线程呢?
用户(活动)线程
| 序号 | 名称 | 说明 |
| 1 | JMX server connection timeout | jmx相关线程 |
| 2 | RMI Scheduler | 实现远程方法调用的线程。 |
| 3 | Monitor Ctrl-Break | idea调试线程 |
| 4 | Attach Listener | 监听客户端请求的线程。 |
| 5 | Signal Dispatcher | 和Attach Listener配合,将接收到的请求转发到指定的处理模块。 |
| 6 | Finalizer | 执行对象finalizer方法的线程,用于垃圾收集 |
| 7 | Reference Handler | 处理强引用、软引用、弱引用、虚引用对象回收的线程 |
| 8 | main | 含有main方法的线程 |
守护线程
| 1 | GC task thread | 垃圾回收线程,根据不同的垃圾收集器,会有不同的线程。 |
| 2 | VM Thread | 所有线程的根线程。有点类似于管理线程的线程。源码在vmThread.hpp |
| 3 | VM Periodic Task Thread | 该线程是JVM周期性任务调度的线程,它由WatcherThread创建,是一个单例对象。该线程在JVM内使用得比较频繁,比如:定期的内存监控、JVM运行状况监控,还有我们经常需要去执行一些jstat这类命令查看gc的情况 |
| 4 | CompilerThread | 用来调用JITing,实时编译装卸class。通常,jvm会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,例如:CompilerThread1 |
这里每个线程都会关联着一个或者多个技术,很多的开发者可能看到这里并不知道在说什么,这非常正常。单说一个Reference Handler线程,如果你不了解Java虚拟机的规范,不了解Java的引用机制,是很难理解透彻这个线程到底是干嘛的。
本篇文章也不可能完全带你把这些线程底层的知识点全部啃透,那估计要写一本书了。笔者只是希望能够通过本篇文章作为一个切入点,切入到并发编程、Java虚拟机的内容里面来。也是希望通过一个让多数人感到好奇的例子能够激起你对并发和jvm的好奇心。
划重点:本文后续不管是讲常用工具,Java引用类型,Finalier以及垃圾回收,都是简单介绍,读者能够知道有这么回事就可以了。详细内容会在后续文章中逐渐丰富,也希望大家能够关注我。
常用的监控工具
工欲善其事必先利其器,我们要进入到Java进程的内部,就需要有顺手的工具。Java发展了很多年了,这期间产生了很多的工具,有命令行的、图形化界面的。有JDK官方提供的,也有三方公司开源的。
这些工具功能都非常强大,不仅可以监控线程信息,还可以监控进程中内存的使用情况,垃圾回收情况,甚至可以运行时修改内存数据等。
下面以监控线程信息为主题对几种常用的工具做一个简单的介绍,这些工具也是以后更深入了解jvm,处理线上问题必备的。
jstack
jstack是jdk自带的基于命令行的工具,可以直接通过jstack pid的形式拉取进程的线程信息。

你说pid是怎么获取的?pid是Java的进程号,可以直接通过另一个命令快速获取,这个命令是 jps -l。直接通过这个命令就可以知道所有Java进程对应的进程号了。
利用命令行看到的信息,就是Java线程运行的栈信息。对于一个正式的项目,线程数量非常多的情况下,看起来还是非常痛苦的。下面介绍一款图形化的工具jConsole
jConsole
jConsole也是jdk自带的工具,jConsole是图形化界面的工具,可以以图形化的方式来监控进程信息。

这个看起来是不是比起来命令行就好多了,左侧展示的是当前所有运行着的线程,右侧是线程的堆栈信息。
既然有图形化界面的工具,为啥还要用命令行呢?
1、存在的就是合理的!
2、一般的服务器上是没有安装图形化插件的,因此我们在处理线上问题的时候,一般就只能选择命令行工具了。
3、因为命令行工具在分析问题时确实不方便,因此我们会把线上的堆栈信息dump到本地,然后再导入到图形化工具中进行分析。
JVisualVM
JVisualVM也是一款非常有名的图形化的监控工具,目前也已经集成到了JDK中,可以直接通过jvusualvm命令启动。我们本篇文章的封面就是通过JVisualVM监控到的。
通过和JConsole进行比较,我们不难发现,JVisualVM的界面要漂亮一些,另外JVisualVM也支持更多的插件。

arthas
arthas是阿里开源的一款基于命令行的Java诊断工具,虽然是命令行工具,但人机交互非常的好,既具备直接在服务器上定位问题的方便性(无图形界面)又具备非常好的交互性,所以对我们的线程问题定位非常方便,目前非常火爆。官方文档:http://arthas.gitee.io/
通过arthas监控到的线程信息如下,我们可以清楚的看到,虽然基于命令行,但布局以及展示效果和图形化界面无异。

庐山真面目
认识了这些工具之后,我们可以非常轻松的看到后台运行着的线程。那么我们就挨个的来分析下,看看这些默默付出的线程是不是在使用我们宝贵的CPU资源划水~
用户线程

一个一个的来看,他们都是干什么的!
main
这个线程大家再熟悉不过了,即main方法运行所在的线程。当Java虚拟机经过类加载等一系列过程之后,找到main方法的类,执行main方法。就会自动在栈内存中创建一个名称为main的线程。main线程是任何一个Java进程都必须存在的一个线程,因为Java进程的入口方法一定是main方法。
在main方法执行完毕后,main线程还会存在吗?
不会。所有的线程,只要线程方法执行完毕,线程栈就会自动销毁,即便是main线程也是这样。这也是为什么在分配内存的时候有一个栈上内存分配,可以随着线程的结束,内存自动释放。
再思考一个问题,我们之前提到过一个问题,所有的用户线程执行完毕后,即便有守护线程在执行,程序也会退出。那么,main线程销毁之后,Java虚拟机是如何判定是否还有其他的用户进程的呢?这就引入了下一个默默守护的线程:DestroyJavaVM
DestroyJavaVM
这个线程在JVisualVM中监控不到,因为我们使用的案例代码中,主线程一直没有销毁。当主线程销毁之后,会自动调用jni_DestroyJavaVM()方法,调起DestoryJavaVM线程。
通过名字就可以知道,这个线程的目的就是为了销毁Java虚拟机。而且是在等待销毁的指令进行销毁。每个用户线程在执行完毕之后,都会判断自己是否是最后一个非守护线程,如果是,则通知DestoryJavaVM销毁Java进程。
Java运行时内存结构中,有栈内存和本地方法栈,这个线程就是运行在本地方法栈中的。我们是无法跟踪到这个线程的堆栈信息。
Attach Listener
Attach Listener从字面意思理解,连接监听器线程。这个线程有点类似于Oracle的监听,用于接收请求,并将请求转发到Signal DIspatcher线程,然后得到响应后封装结果响应给客户端。
这个线程也是运行在本地方法栈中。
Signal Dispatcher
Signal Dispatcher 线程和Attach Listener是成对出现的。
从字面理解为信号转发,这个线程在接收到Attach Listener的请求,根据请求数据转发到不同的模块进行处理,并将处理的结果返回给Attach Listener。
这个线程运行在本地方法栈中。
Reference Handler
处理弱引用、虚引用、强引用、软引用 的垃圾回收问题。
至于这四种引用类型,简单来说,就是对象除了有用和无用之间,还有些食之无味、弃之可惜的对象,于是就把对象分成了四种类型。级别越低,越容易被垃圾回收。
Finalizer
执行对象finalizer方法的线程,用于垃圾收集
Finalizer线程是个单一职责的线程。这个线程会不停的循环等待java.lang.ref.Finalizer.ReferenceQueue中的新增对象。一旦Finalizer线程发现队列中出现了新的对象,它会弹出该对象,调用它的finalize()方法,将该引用从Finalizer类中移除,因此下次GC再执行的时候,这个Finalizer实例以及它引用的那个对象就可以回垃圾回收掉了。
JMX server connection timeout
JMX即Java Management Extensions,是一种Java扩展管理的技术。这个技术是一个框架,提供了扩展的接口,如MBean。同时也是一个协议,即jmx协议,通过这个协议可以跨平台的监控Java进程的信息。
JDK中的类,有很多实现了MBean,那么我们就可以通过MBean来进行监控和调整系统的状态。如下面的java.lang.Runtime就实现了MBean接口,对外暴露了一些的属性,我们通过jmx协议就可以监控到这些属性的值。

这里为什么会有一个jmx server connection timeout 线程呢?因为我没有开启jmx的远程访问。开启之后,就不会出现这个线程了。
可能大家发现,用jmx来实现的功能,通过http不也能实现吗?是的。大家可以思考下,为什么会有jmx。
RMI Scheduler
RMI 全称为 Remote Mehod Invocation 是一种用于实现远程方法调用的JavaAPI。
Monitor Ctrl-Break
这个是在idea启动的项目中才有的线程,是用于idea代码调试使用的。
守护线程
在图形化的界面中都没有直接展示出来非活动的守护进程。如果想要查看非活动那个的进程,需要导出线程信息来看,截图如下。

VM Thread
所有线程的根线程,有点类似于管理线程的线程。源码在vmThread.hpp
GC task thread
用于垃圾回收的线程,这个线程和使用的垃圾收集器有很大的关系,如果使用serial收集器,那么就只会启动一个垃圾收集线程,用于单线程的垃圾收集工作。我这里使用的是Parallel垃圾收集器,默认启动的线程数是我的CPU的线程数,我CPU是4核心8线程的,因此,这里能够看到8个gc task thread,编号一次是0-8。
VM Periodic Task Thread
该线程是JVM周期性任务调度的线程,它由WatcherThread创建,是一个单例对象。该线程在JVM内使用得比较频繁,比如:定期的内存监控、JVM运行状况监控,还有我们经常需要去执行一些jstat这类命令查看gc的情况。
CompilerThread
我们似乎已经忘记了,Java是编译型语言还是解释型语言。回想一下,那是很多年以前,你拿着一本Java的书,还在为是否进入Java的大门而徘徊,你会深刻的记得,Java一次编写到处运行,Java是面向对象的,Java...

说了那么多,其实就是想说,对于即时编译方式,依赖的就是一个称之为JIT的即时编译器。这个编译器在工作的时候会引入多个线程,就是CompilerThread。
总结
为了我们的代码能够运行,jvm后台启动了很多的线程,各司其职,分工协作。他们在需要的时候启动,在不需要的时候销毁,不图回报,任劳任怨。
每个线程都是久经沙场的老将,在Java历史的潮流中浮沉。他们的背后都有着强大的技术的支撑,我们通过学习这些线程背后的内容,可以更加深入的了解Java虚拟机的运行机制。
下一篇,闲话哥将带大家深入的看看Java的监控工具,并利用监控工具来完成对线上问题的处理。
>> 链接文章(后续完善哈,先把提纲列在这里)
Java常用监控工具
arthas监控工具的使用
jconsole监控工具的使用
jvisualvm监控工具的使用
弱引用、虚引用、强引用、软引用
finalizer是如何运转的
jmx技术探究
VM Thread探究
Java垃圾回收

闲话Java
微信号|xianhuajava
邮箱|18366131816@163.com




