感谢同事[觉梦]投递此稿。前端
hi,alljava
最近抽时间把JVM运行过程当中产生的一些线程进行了整理,主要是围绕着咱们系统jstack生成的文件为参照依据。 前段时间由于系统代码问题,形成性能瓶颈,因而就dump了一份stack出来进行分析。 stack 里面线程很是多,排查起来须要必定的经验,因此,对它们有必定了解,能够提升排查问题的效率。 如今网上资料也不是特别全,因此,致使不少新人在拿到一个stack文件以后,也不知知道从何看起。 下面我把此次整理的一些我的认为比较常见的线程列出来。后端
线程 | 所属 | 说明 |
Attach Listener | JVM | Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的而且吧结果返回给发送者。一般咱们会用一些命令去要求jvm给咱们一些反馈信息,如:java -version、jmap、jstack等等。若是该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,获得启动。 |
Signal Dispatcher | JVM | 前面咱们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不一样的模块处理命令,而且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工做。 |
CompilerThread0 | JVM | 用来调用JITing,实时编译装卸class。一般,jvm会启动多个线程来处理这部分工做,线程名称后面的数字也会累加,例如:CompilerThread1 |
Concurrent Mark-SweepGC Thread | JVM | 并发标记清除垃圾回收器(就是一般所说的CMS GC)线程,该线程主要针对于老年代垃圾回收。ps:启用该垃圾回收器,须要在jvm启动参数中加上:-XX:+UseConcMarkSweepGC |
DestroyJavaVM | JVM | 执行main()的线程在main执行完后调用JNI中的jni_DestroyJavaVM()方法唤起DestroyJavaVM线程。 JVM在Jboss服务器启动以后,就会唤起DestroyJavaVM线程,处于等待状态,等待其它线程(java线程和native线程)退出时通知它卸载JVM。线程退出时,都会判断本身当前是不是整个JVM中最后一个非deamon线程,若是是,则通知DestroyJavaVM线程卸载JVM。ps:扩展一下:1.若是线程退出时判断本身不为最后一个非deamon线程,那么调用thread->exit(false),并在其中抛出thread_end事件,jvm不退出。2.若是线程退出时判断本身为最后一个非deamon线程,那么调用before_exit()方法,抛出两个事件: 事件1:thread_end线程结束事件、事件2:VM的death事件。
而后调用thread->exit(true)方法,接下来把线程从active list卸下,删除线程等等一系列工做执行完成后,则通知正在等待的DestroyJavaVM线程执行卸载JVM操做。数组 |
ContainerBackgroundProcessor | JBOSS | 它是一个守护线程,在jboss服务器在启动的时候就初始化了,主要工做是按期去检查有没有Session过时.过时则清除.参考:http://liudeh-009.iteye.com/blog/1584876 |
ConfigClientNotifier | ConfigServer | ConfigServer服务端当有配置变动时,就会将最新的配置推送到ConfigServer客户端的一个数据列队中,ConfigClientNotifier线程用于按期检查该数据列队中是否有数据,若是有数据,则将数据分发到订阅该数据的组件去作业务逻辑,好比:tair和hsf的数据都订阅了ConfigServer数据源,当ConfigClientNotifier线程发现数据有更新时,就触发作数据分发特定特定信号标识将数据分发到相应的订阅者。 |
ConfigClientWorker-Default | ConfigServer | 包括主动向服务器端发送数据(主要是订阅和发布的数据)和接收服务器推送过来的数据(主要是订阅数据的值) 。 |
Dispatcher-Thread-3 | Log4j | Log4j具备异步打印日志的功能,须要异步打印日志的Appender都须要注册到AsyncAppender对象里面去,由AsyncAppender进行监听,决定什么时候触发日志打印操做。AsyncAppender若是监听到它管辖范围内的Appender有打印日志的操做,则给这个Appender生成一个相应的event,并将该event保存在一个buffuer区域内。 Dispatcher-Thread-3线程负责判断这个event缓存区是否已经满了,若是已经满了,则将缓存区内的全部event分发到Appender容器里面去,那些注册上来的Appender收到本身的event后,则开始处理本身的日志打印工做。Dispatcher-Thread-3线程是一个守护线程。 |
Finalizer线程 | JVM | 这个线程也是在main线程以后建立的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;关于Finalizer线程的几点:1)只有当开始一轮垃圾收集时,才会开始调用finalize()方法;所以并非全部对象的finalize()方法都会被执行;2)该线程也是daemon线程,所以若是虚拟机中没有其余非daemon线程,无论该线程有没有执行完finalize()方法,JVM也会退出;3) JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;4) JVM为何要单独用一个线程来执行finalize()方法呢?若是JVM的垃圾收集线程本身来作,颇有可能因为在finalize()方法中误操做致使GC线程中止或不可控,这对GC线程来讲是一种灾难; |
Gang worker#0 | JVM | JVM用于作新生代垃圾回收(monir gc)的一个线程。#号后面是线程编号,例如:Gang worker#1 |
GC Daemon | JVM | GC Daemon线程是JVM为RMI提供远程分布式GC使用的,GC Daemon线程里面会主动调用System.gc()方法,对服务器进行Full GC。 其初衷是当RMI服务器返回一个对象到其客户机(远程方法的调用方)时,其跟踪远程对象在客户机中的使用。当再没有更多的对客户机上远程对象的引用时,或者若是引用的“租借”过时而且没有更新,服务器将垃圾回收远程对象。不过,咱们如今jvm启动参数都加上了-XX:+DisableExplicitGC配置,因此,这个线程只有打酱油的份了。 |
IdleRemover | JBOSS | Jboss链接池有一个最小值,该线程每过一段时间都会被Jboss唤起,用于检查和销毁链接池中空闲和无效的链接,直到剩余的链接数小于等于它的最小值。 |
Java2D Disposer | JVM | 这个线程主要服务于awt的各个组件。提及该线程的主要工做职责前,须要先介绍一下Disposer类是干吗的。Disposer提供一个addRecord方法。若是你想在一个对象被销毁前再作一些善后工做,那么,你能够调用Disposer#addRecord方法,将这个对象和一个自定义的DisposerRecord接口实现类,一块儿传入进去,进行注册。Disposer类会唤起“Java2D Disposer”线程,该线程会扫描已注册的这些对象是否要被回收了,若是是,则调用该对象对应的DisposerRecord实现类里面的dispose方法。Disposer实际上不限于在awt应用场景,只是awt里面的不少组件须要访问不少操做系统资源,因此,这些组件在被回收时,须要先释放这些资源。 |
FelixDispatchQueue | Sofa | 该线程会在sofa启动时会唤起该线程,该线程用于分发OSGI事件到Declarative Services 中去发布,查找,绑定目标服务。其实,咱们接口配置的service和reference就涉及到服务的发布、查找和绑定工做。 Declarative Services 主要工做职责是方便地对服务之间的依赖关系和状态进行监听和管理。OSGI使用事件策略去调用Declarative Services中的服务。 |
InsttoolCacheScheduler_QuartzSchedulerThread | Quartz | InsttoolCacheScheduler_QuartzSchedulerThread是Quartz的主线程,它主要负责实时的获取下一个时间点要触发的触发器,而后执行触发器相关联的做业。原理大体以下:Spring和Quartz结合使用的场景下,Spring IOC容器初始化时会建立并初始化Quartz线程池(TreadPool),并启动它。刚启动时线程池中每一个线程都处于等待状态,等待外界给他分配Runnable(持有做业对象的线程)。继而接着初始化并启动Quartz的主线程(InsttoolCacheScheduler_QuartzSchedulerThread),该线程自启动后就会处于等待状态。等待外界给出工做信号以后,该主线程的run方法才实质上开始工做。run中会获取JobStore中下一次要触发的做业,拿到以后会一直等待到该做业的真正触发时间,而后将该做业包装成一个JobRunShell对象(该对象实现了Runnable接口,其实看是上面TreadPool中等待外界分配给他的Runnable),而后将刚建立的JobRunShell交给线程池,由线程池负责执行做业。线程池收到Runnable后,从线程池一个线程启动Runnable,反射调用JobRunShell中的run方法,run方法执行完成以后,TreadPool将该线程回收至空闲线程中。 |
InsttoolCacheScheduler_Worker-2 | Quartz | InsttoolCacheScheduler_Worker-2线程就是ThreadPool线程的一个简单实现,它主要负责分配线程资源去执行InsttoolCacheScheduler_QuartzSchedulerThread线程交给它的调度任务(也就是JobRunShell)。 |
java.util.concurrent.ThreadPoolExecutor$Worker | JVM | |
JBossLifeThread | Jboss | Jboss主线程启动成功,应用程序部署完毕以后将JBossLifeThread线程实例化而且start,JBossLifeThread线程启动成功以后就处于等待状态,以保持Jboss Java进程处于存活中。 所得比较通俗一点,就是Jboss启动流程执行完毕以后,为何没有结束?就是由于有这个线程hold主了它。牛b吧~~ |
JBoss System Threads(1)-1 | Jboss | 该线程是一个socket服务,默认端口号为:1099。主要用于接收外部naming service(Jboss JNDI)请求。 |
JCA PoolFiller | Jboss | 该线程主要为JBoss内部提供链接池的托管。 简单介绍一下工做原理 :Jboss内部凡有远程链接需求的类,都须要实现ManagedConnectionFactory接口,例如须要作JDBC链接的XAManagedConnectionFactory对象,就实现了该接口。而后将XAManagedConnectionFactory对象,还有其它信息一块儿包装到InternalManagedConnectionPool对象里面,接着将InternalManagedConnectionPool交给PoolFiller对象里面的列队进行管理。 JCA PoolFiller线程会按期判断列队内是否有须要建立和管理的InternalManagedConnectionPool对象,若是有的话,则调用该对象的fillToMin方法,触发它去建立相应的远程链接,而且将这个链接维护到它相应的链接池里面去。 |
JDWP Event Helper Thread | JVM | JDWP是通信交互协议,它定义了调试器和被调试程序之间传递信息的格式。它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的JVMTI和JDI的通讯通畅。 该线程主要负责将JDI事件映射成JVMTI信号,以达到调试过程当中操做JVM的目的。 |
JDWP Transport Listener: dt_socket | JVM | 该线程是一个Java Debugger的监听器线程,负责受理客户端的debug请求。一般咱们习惯将它的监听端口设置为8787。 |
Low Memory Detector | JVM | 这个线程是负责对可以使用内存进行检测,若是发现可用内存低,分配新的内存空间。 |
process reaper | JVM | 该线程负责去执行一个OS命令行的操做。 |
Reference Handler | JVM | JVM在建立main线程后就建立Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象自己(软引用、弱引用、虚引用)的垃圾回收问题。 |
Surrogate Locker Thread (CMS) | JVM | 这个线程主要用于配合CMS垃圾回收器使用,它是一个守护线程,其主要负责处理GC过程当中,Java层的Reference(指软引用、弱引用等等)与jvm内部层面的对象状态同步。这里对它们的实现稍微作一下介绍:这里拿WeakHashMap作例子,将一些关键点先列出来(咱们后面会将这些关键点所有串起来):
1. 咱们知道HashMap用Entry[]数组来存储数据的,WeakHashMap也不例外,内部有一个Entry[]数组。缓存 2. WeakHashMap的Entry比较特殊,它的继承体系结构为Entry->WeakReference->Reference。服务器 3. Reference里面有一个全局锁对象:Lock,它也被称为pending_lock. 注意:它是静态对象。并发 4. Reference 里面有一个静态变量:pending。异步 5. Reference 里面有一个静态内部类:ReferenceHandler的线程,它在static块里面被初始化而且启动,启动完成后处于wait状态,它在一个Lock同步锁模块中等待。jvm 6. 另外,WeakHashMap里面还实例化了一个ReferenceQueue列队,这个列队的做用,后面会提到。socket 7. 上面关键点就介绍完毕了,下面咱们把他们串起来。 假设,WeakHashMap对象里面已经保存了不少对象的引用。JVM在进行CMS GC的时候,会建立一个ConcurrentMarkSweepThread(简称CMST)线程去进行GC,ConcurrentMarkSweepThread线程被建立的同时会建立一个SurrogateLockerThread(简称SLT)线程而且启动它,SLT启动以后,处于等待阶段。CMST开始GC时,会发一个消息给SLT让它去获取Java层Reference对象的全局锁:Lock。直到CMS GC完毕以后,JVM会将WeakHashMap中全部被回收的对象所属的WeakReference容器对象放入到Reference的pending属性当中(每次GC完毕以后,pending属性基本上都不会为null了),而后通知SLT释放而且notify全局锁:Lock。此时激活了ReferenceHandler线程的run方法,使其脱离wait状态,开始工做了。ReferenceHandler这个线程会将pending中的全部WeakReference对象都移动到它们各自的列队当中,好比当前这个WeakReference属于某个WeakHashMap对象,那么它就会被放入相应的ReferenceQueue列队里面(该列队是链表结构)。当咱们下次从WeakHashMap对象里面get、put数据或者调用size方法的时候,WeakHashMap就会将ReferenceQueue列队中的WeakReference依依poll出来去和Entry[]数据作比较,若是发现相同的,则说明这个Entry所保存的对象已经被GC掉了,那么将Entry[]内的Entry对象剔除掉。 |
taskObjectTimerFactory | JVM | 顾名思义,该线程就是用来执行任务的。当咱们把一个任务交给Timer对象,而且告诉它执行时间,周期时间后,Timer就会将该任务放入任务列队,而且通知taskObjectTimerFactory线程去处理任务,taskObjectTimerFactory线程会将状态为取消的任务从任务列队中移除,若是任务是非重复执行类型的,则在执行完该任务后,将它从任务列队中移除,若是该任务是须要重复执行的,则计算出它下一次执行的时间点。 |
VM Periodic Task Thread | JVM | 该线程是JVM周期性任务调度的线程,它由WatcherThread建立,是一个单例对象。该线程在JVM内使用得比较频繁,好比:按期的内存监控、JVM运行情况监控,还有咱们常常须要去执行一些jstat这类命令查看gc的状况,以下:jstat -gcutil 23483 250 7 这个命令告诉jvm在控制台打印PID为:23483的gc状况,间隔250毫秒打印一次,一共打印7次。 |
VM Thread | JVM | 这个线程就比较牛b了,是jvm里面的线程母体,根据hotspot源码(vmThread.hpp)里面的注释,它是一个单例的对象(最原始的线程)会产生或触发全部其余的线程,这个单个的VM线程是会被其余线程所使用来作一些VM操做(如,清扫垃圾等)。在 VMThread的结构体里有一个VMOperationQueue列队,全部的VM线程操做(vm_operation)都会被保存到这个列队当中,VMThread自己就是一个线程,它的线程负责执行一个自轮询的loop函数(具体能够参考:VMThread.cpp里面的void VMThread::loop()),该loop函数从VMOperationQueue列队中按照优先级取出当前须要执行的操做对象(VM_Operation),而且调用VM_Operation->evaluate函数去执行该操做类型自己的业务逻辑。ps:VM操做类型被定义在vm_operations.hpp文件内,列举几个:ThreadStop、ThreadDump、PrintThreads、GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark…..有兴趣的同窗,能够本身去查看源文件。 |
若是有同窗发现有什么写得不对或者不全的地方,也麻烦留言补充纠正一下 ,多谢了 ( ^_^ ) 。
由于java线程仍是比较多了,有时间再继续整理了,有兴趣的同窗也能够去作进一步深刻了解。
(全文完)