STW知识分享
前置内容GC时的Stop the World(STW)是大家最大的敌人
https://www.jianshu.com/p/314272e6d35b
stop the world:
在发生GC时会停下所有的用户线程,从而导致Java程序出现全局停顿的无响应情况,而这种情况则被称为STW(Stop The World)世界暂停。在发生STW之后,所有的Java代码会停止运行,不过native代码是可以继续执行的,但也不能和JVM交互。一般发生STW都是由于GC引起的,但在某几种少数情况下,也会导致STW出现,如线程Dump、死锁检查、堆日志Dump等
GC发生时为什么都必须要STW呢
- 一个是尽量为了避免浮动垃圾产生
- 就是刚刚标记完成一块区域中的对象,但转眼用户线程又在该区域中产生了新的“垃圾”。
- 第二个则是为了确保一致性
- 分析工作必须在一个能确保一致性的快照中进行,不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话分析结果的准确性无法得到保证。
STW是不可避免的,垃圾回收算法执⾏一定会出现STW,我们要做的只是减少停顿的时间
1. 什么时候停? 1. Garbage collection pauses(垃圾回收) 2. JIT相关,比如Code deoptimization, Flushing code cache 3. Class redefinition (e.g. javaagent,AOP代码植入的产生的instrumentation) 4. Biased lock revocation 取消偏向锁 5. Various debug operation (e.g. thread dump or deadlock check) dump 线程 2. 停在哪? 3. 如何停? OopMap
ordinary object pointer 普通对象指针,*OopMap* 用于枚举 GC Roots,记录**栈**中引用数据类型的位置。
GC Roots 枚举的过程中,是需要暂停用户线程的,对栈进行扫描,找到哪些地方存储了对象的引用。然而,栈存储的数据不止是对象的引用,因此对整个栈进行全量扫描,显然是很耗费时间,影响性能的。
因此,在 HotSpot 中采取了空间换时间的方法,使用 OopMap 来存储栈上的对象引用的信息。
在 GC Roots 枚举时,只需要遍历每个栈桢的 OopMap,通过 OopMap 存储的信息,快捷地找到 GC Roots
OopMap 中存储了两种对象的引用:
◉ 栈里和寄存器内的引用
在即时编译中,在特定的位置记录下栈里和寄存器里哪些位置是引用◉ 对象内的引用
类加载动作完成时,HotSpot 就会计算出对象内什么偏移量上是什么类型的数据
注:把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为有效地址或偏移量,因此,实际地址=所在段的起始地址+偏移量
因此,可以理解为 OopMap 就是商场的商品清单,清单上记录着每一种商品的所在位置和数量,通过清单可以直接到对应的货架上找到商品。
如果没有这份清单,需要寻找一件商品的时候,就只能从头开始,按顺序翻找每一个货架上的商品,直到找到对应的商品。
safe point 如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题
然而,在程序执行的过程中,对象之间的引用关系随时都会发生改变,这意味着对应的 OopMap 需要同步进行更新。
如果每一条指令的执行,都生成(或更新)对应的OopMap,那么将会占用大量的内存空间,增加了 GC 的空间成本。
因此,针对这个问题,JVM 引入了 Safe Point 的概念,只有在 Safe Point 才会生成(或更新)对应的 OopMap。
Safe Point 就是一个安全点,可以理解为用户线程执行过程中的一些特殊位置。线程执行到 Safe Point 的时候,OopMap 保存了当前线程的上下文,当线程执行到这些位置的时候,说明线程当前的状态是确定的,线程有哪些对象、使用了哪些内存。
那么,哪些地方适合放置 Safe Point?
◉ 循环结束
◉ 所有的非计数循环的末尾(防止循环体的执行时间太长,一直进入不了 Safe Point)
◉ 所有方法返回之前
◉ 调用方法的call之后
◉ 抛出异常的位置
如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题
一个线程为一个栈,一个栈由多个栈桢组成,一个栈桢对应一个方法,一个方法有多个安全点。GC发生时,程序首先运行到最近的一个安全点停下来,然后更新自己的OopMap,记录栈上哪些位置代表着引用。枚举根节点时,递归遍历每个栈桢的OopMap ,通过栈中记录的被引用的对象内存地址,即可找到这些对象(GC Roots)
Safe Region如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题
安全点的使用似乎解决了OopMap计算的效率的问题,但是这里还有一个问题。安全点需要程序自己跑过去,那么对于那些已经停在路边休息或者看风景的程序(比如那些处在Sleep或者Blocked状态的线程),他们可能并不会在很短的时间内跑到安全点去。所以这里为了解决这个问题,又引入了安全区域的概念。
安全区域很好理解,就是在程序的一段代码片段中并不会导致引用关系发生变化,也就不用去更新OopMap表了,那么在这段代码区域内任何地方进行GC都是没有问题的。这段区域就称之为安全区域。线程执行的过程中,如果进入到安全区域内,就会标志自己已经进行到安全区域了。那么虚拟机要进行GC的时候,就不会管这些已经运行到安全区域的线程,当线程要脱离安全区域的时候,要自己检查系统是否已经完成了GC或者根节点枚举(这个跟GC的算法有关系),如果完成了就继续执行,如果未完成,它就必须等待收到可以安全离开安全区域的Safe Region的信号为止。
当线程运行到Safe Region的代码时,首先标识已经进入了Safe Region,如果这段时间内发生GC,JVM会忽略标识为Safe Region状态的线程;
当线程即将离开Safe Region时,会检查JVM是否已经完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region的信号为止。
如何停在安全点1.抢先式中断:(目前没有虚拟机采用了)
首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
2.主动式中断:
设置一个中断标志,各个线程运行到Safe Point的时候主动轮训这个标志,如果中断标志位真,则将自己进行中断挂起。
查看safe point日志打印Safepoint统计信息:
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
- vmop: 引发STW的原因,以及触发时间,本例中是GC。该项常见的输出有:RevokeBias、BulkRevokeBias、Deoptimize、G1IncCollectionPause。
- total : STW发生时,JVM存在的线程数目。
- initially_running : STW发生时,仍在运行的线程数,这项是Spin阶段的 时间来源
- wait_to_block : STW需要阻塞的线程数目,这项是block阶段的时间来源
- sync = spin + block + 其他。
打印进入Safepoint慢的线程:
-XX:+SafepointTimeout
-XX:SafepointTimeoutDelay=2000
Thread.sleep()会进入safe Point
主动式中断不会直接中断线程,而是全局设置一个标志位,用户线程会不断的轮询这个标志位;当发现标志位为真时,线程会在最近的一个安全点主动中断挂起
标签:Safe,知识,安全,OopMap,STW,GC,线程,分享 From: https://www.cnblogs.com/yanghanwen/p/16984004.html