STW理解
每当聊起GC时会不可避免的提到stw(stop the world简写),即除JVM工作线程外,中断所有用户进程,程序全局暂停;本质是通过挂起线程来阻断对JVM的操作。比如在垃圾回收时,首先进行对象引用的可达性分析并对可回收的对象进行标记;为了保证标记过程中引用关系的一致性,需要stw操作。
发生GC时,不论哪种垃圾回收器都不可避免的需要stw操作。分代回收场景中,新生代进行垃圾回收时会全程stw(因为基于标记复制算法),老生代回收主要是在可达性分析时需要stw操作;比如CMS垃圾回收器的四个阶段:
- 1.初始标记(需要stw来快速扫描GC roots根节点);
- 2.并发标记;
- 3.重新标记(需要stw来对并发标记过程中导致的引用关系来重新确认全局标记结果);
- 4.并发清除;
Tips:除GC外,堆转储等操作也会进行stw操作;而且为了便于分析和压缩转储文件大小,转储前都会手动进行一次GC。
JVM调优的核心目的是提升吞吐量,除了高质量鲁棒的代码,还要花费精力来优化GC策略,jvm吞吐量的计算公式如下:
GC耗时中stw占了很大比重,是调优时必须面对的顽固分子;无法被消灭,只能通过手段来调和敌对关系。
安全点(safe point)
STW操作时为了方便线程中断管理,提出了安全点的概念,所有的线程在安全点位置挂起等待。JVM在进行可达性分析时,需要枚举遍历GC Roots,如果针对引用链挨个遍历,在几百上千兆的内存大小遍历也是非常耗时间的。所以HotSpot虚拟机用OopMap(Ordinary Object Pointer map集合)来记录对象内的引用关系,只有在特殊的指令或者特定的位置才会产生或者更新OopMap,这个特定的位置即为安全点。
安全点的选择是有策略的,需要具备“是否具有长时间运行指令”的特征。我们知道,CPU资源是时间片段,如果在占用cpu时间比较小的指令位置设置安全点,线程的中断操作导致的全局暂停会效果会被放大;反之,在需要长时间运行的指令位置进行中断操作,延迟效果会被覆盖。
线程中断策略
安全点上线程的中断策略有两种:抢先式中断和主动式中断。
抢先式中断
抢先式中断不需要用户线程配合,在进行GC时,JVM会中断挂起所有用户线程;如果有些线程不在安全点,过段时间再尝试,直到线程在安全点的位置成功中断。抢先式中断的最大问题是时间成本的不可控,进而导致性能不稳定和吞吐量的波动,特别是在高并发场景下这是非常致命的,作为程序猿我们习惯掌控一切。目前没有虚拟机采用这种中断策略。
主动式中断
主动式中断不会直接中断线程,而是全局设置一个标志位,用户线程会不断的轮询这个标志位;当发现标志位为真时,线程会在最近的一个安全点主动中断挂起。
安全区域(safe region)
在安全点上中断的是活跃运行的用户线程,对于已经挂起的线程该怎么处理呢?已经挂起的线程会被认定为处在安全区域内,中断的时候不需要考虑安全区域中的线程;当前安全区域的线程要被唤醒离开安全区域时,需要校验一下主动式中断策略的标志位是否为真,如果为真则继续挂起等待,否则则可被唤醒继续执行。