暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

扒一扒那些在后台默默付出的“线程”

闲话Java 2021-08-19
1194

哈喽,大家好,欢迎阅读闲话Java的第二期:《扒一扒那些在后台默默付出的-线程》。本期由闲话哥带您深入Java进程的内部,看一看到底有哪些默默付出的线程!


用户线程和守护线程

要知道有哪些默默付出的线程,先要对线程进行一个简单的分类。线程从大类上可以分为两类,分别是用户线程守护线程通过Thread.setDaemon(false)设置为用户线程,通过Thread.setDaemon(true)设置为守护线程。如果不设置此属性,默认为用户线程。

用户线程和守护线程最大的区别就是,如果没有正在运行中的用户线程,即便守护线程在执行,程序也会强制中断守护线程并退出程序。

从创建线程的角色上来看,也可以分为两类,分别是自定义线程和预定义(系统)线程。这个非常好理解,我们自己创建的线程称为自定义线程,系统默认启动的线程为预定义线程。

划重点:我们本篇要扒的就是预定义(系统)线程,也就是用户预定义线程和守护预定义线程。


结论

文章比较长,先说结论。那么到底有哪些默默付出的线程呢?

用户(活动)线程

序号
名称
说明
1JMX server connection timeout  jmx相关线程
2RMI Scheduler 实现远程方法调用的线程。
3Monitor Ctrl-Breakidea调试线程
4Attach Listener监听客户端请求的线程。
5Signal Dispatcher和Attach Listener配合,将接收到的请求转发到指定的处理模块。
6Finalizer执行对象finalizer方法的线程,用于垃圾收集
7Reference Handler处理强引用、软引用、弱引用、虚引用对象回收的线程
8main含有main方法的线程

守护线程

1GC 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

文章转载自闲话Java,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论