《JVM G1源码分析和调优》 —2.6 线程

举报
华章计算机 发表于 2019/12/20 13:25:50 2019/12/20
【摘要】 本节书摘来自华章计算机《JVM G1源码分析和调优》 一书中第2章,第2.6.1节,作者是彭成寒。

2.6 线程

线程是程序执行的基本单元,在JVM中也定义封装了线程。图2-1是JVM的线程类图。

 image.png

图2-1 JVM线程类结构图

这里只介绍G1中涉及的几类线程:

JavaThread:就是要执行Java代码的线程,比如Java代码的启动会创建一个JavaThread运行;对于Java代码的启动,可以通过JNI_CreateJavaVM来创建一个JavaThread,而对于一般的Java线程,都是调用java.lang.thread中的start方法,这个方法通过JNI调用创建JavaThread对象,完成真正的线程创建。

CompilerThread:执行JIT的线程。

WatcherThread:执行周期性任务,JVM里面有很多周期性任务,例如内存管理中对小对象使用了ChunkPool,而这种管理需要周期性的清理动作ChunkPool

Cleaner;JVM中内存抽样任务MemProfilerTask等都是周期性任务。

NameThread:是JVM内部使用的线程,分类如图2-1所示。

VMThread:JVM执行GC的同步线程,这个是JVM最关键的线程之一,主要是用于处理垃圾回收。简单地说,所有的垃圾回收操作都是从VMThread触发的,如果是多线程回收,则启动多个线程,如果是单线程回收,则使用VMThread

进行。VMThread提供了一个队列,任何要执行GC的操作都实现了VM_GC_Operation,在JavaThread中执行VMThread::execute(VM_GC_Operation)把GC操作放入到队列中,然后再用VMThread的run方***询这个队列就可以了。当这个队列有内容的时候它就开始尝试进入安全点,然后执行相应的GC任务,完成GC任务后会退出安全点。

ConcurrentGCThread:并发执行GC任务的线程,比如G1中的ConcurrentMark

Thread和ConcurrentG1RefineThread,分别处理并发标记和并发Refine,这两个线程将在混合垃圾收集和新生代垃圾回收中介绍。

WorkerThread:工作线程,在G1中使用了FlexibleWorkGang,这个线程是并行执行的(个数一般和CPU个数相关),所以可以认为这是一个线程池。线程池里面的线程是为了执行任务(在G1中是G1ParTask),也就是做GC工作的地方。VMThread会触发这些任务的调度执行(其实是把G1ParTask放入到这些工作线程中,然后由工作线程进行调度)。

从线程的实现角度来看,JVM中的每一个线程都对应一个操作系统(OS)线程。JVM为了提供统一的处理,设计了JVM线程状态,代码如下所示:

hotspot/src/share/vm/classfile/javaClasses.hpp

 

NEW                            // 新创建线程

RUNNABLE                       // 可运行或者正在运行

SLEEPING                       // 调用Thread.sleep()进入睡眠

IN_OBJECT_WAIT                 // 调用Object.wait()进入等待

IN_OBJECT_WAIT_TIMED           // 调用Object.wait(long)进入等待,带有过期时间

PARKED                         // JVM内部使用LockSupport.park()进入等待

PARKED_TIMED                   // JVM内部使用LockSupport.park(long)进入等待,

                               // 带有过期时间

BLOCKED_ON_MONITOR_ENTER       // 进入一个同步块

TERMINATED                     // 终止

JVM可以运行在不同的操作系统之上,所以它也统一定义了操作系统线程的状态,代码如下所示:

hotspot/src/share/vm/runtime/osThread.hpp

 

ALLOCATED,                    // 线程已经分配但还没初始化

INITIALIZED,                  // 线程已经初始化,还没开始启动

RUNNABLE,                     // 线程已经启动并可被执行或者正在运行

MONITOR_WAIT,                 // 等待一个Monitor

CONDVAR_WAIT,                 // 等待一个条件变量

OBJECT_WAIT,                  // 通过调用Object.wait()等待对象

BREAKPOINTED,                 // 调试状态

SLEEPING,                     // 通过调用Thread.sleep()而进入睡眠

ZOMBIE                        // 僵死状态,待回收

这里定义不同的线程状态有两个目的:第一、统一管理,第二、根据状态可以做一些同步处理,相关内容在VMThread进入安全点时会有涉及。关于安全点的内容并不影响G1的阅读,后文将会详细介绍。

当线程创建时,它的状态为NEW,当执行时转变为RUNNABLE。线程在Windows

和Linux上的实现稍有区别。在Linux上创建线程后,虽然设置成NEW,但是Linux的线程创建完之后就可以执行,所以为了让线程只有在执行Java代码的start之后才能执行,当线程初始化之后,通过等待一个信号将线程暂停,代码如下所示:

hotspot/src/os/linux/vm/os_linux.cpp

 

{

  Monitor* sync_with_child = osthread->startThread_lock();

  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);

  while ((state = osthread->get_state()) == ALLOCATED) {

    sync_with_child->wait(Mutex::_no_safepoint_check_flag);

  }

}

在调用start方法时,发送通知事件,让线程真正运行起来。

2.6.1 栈帧

栈帧(frame)在线程执行时和运行过程中用于保存线程的上下文数据,JVM设计了Java栈帧,这是垃圾回收中最重要的根,栈帧的结构在不同的CPU中并不相同,在x86中代码如下所示:

hotspot/src/cpu/x86/vm/frame_x86.inline.hpp

 

_pc = NULL;                   // 程序计数器,指向下一个要执行的代码地址

_sp = NULL;                    // 栈顶指针

_unextended_sp = NULL;            // 异常栈顶指针

_fp = NULL;                     // fp是栈底指针

_cb = NULL;                    // cb是代码块的地址

_deopt_state = unknown;           // 这个字段描述从编译代码到解释代码反优化的状态

在实际应用中主要使用vframe,它包含了栈帧的字段和线程对象。在JaveThread中定义了JavaFrameAnchor,这个结构保存的是最后一个栈帧的sp、fp。每一个JavaThread都有一个JavaFrameAnchor,即最后一次调用栈的sp、fp。而通过这两个值可以构造栈帧结构,并且根据栈帧的内容,能够遍历整个JavaThread运行时的所有调用链。获取的方法就是根据JavaFrameAnchor里面的sp、fp构造栈帧,再根据栈帧构造vframe结构,代码如下所示:

vframe* start_vf = last_java_vframe(&reg_map);

for (vframe* f = start_vf; f; f = f->sender() ) {

  ……

}

在遍历的时候主要通过sender获得下一个栈,其中sender位于栈帧中,其具体的位置依赖于栈的布局,比如汇编解释器在执行时栈帧的代码如下:

hotspot/src/cpu/x86/vm/frame_x86.hpp

 

栈帧也是和GC密切相关的,在GC过程中,通常第一步就是遍历根,Java线程栈帧就是根元素之一,遍历整个栈帧的方式是通过StackFrameStream,其中封装了一个next指针,其原理和上述的代码一样,通过sender获得调用者的栈帧。

值得一提的是,我们将Java的栈帧作为根来遍历堆,对对象进行标记并收集垃圾。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。