上篇文章深入浅出JVM(十二)之垃圾回收算法探讨了垃圾回收算法,为了可能更加充沛的了解后续的垃圾收集器,本篇文章将深入浅出解析垃圾回收算法的相干细节,如:STW、枚举根节点如何防止长时间STW、平安点与安全区、跨代援用引起的GC Root扫描范畴增大等问题
HotSpot垃圾回收算法细节
STW
Stop The Word
STW: GC中为了剖析垃圾过程确保一致性,会导致所有Java用户线程进展
可达性剖析算法枚举根节点导致STW
因为不停顿线程的话,在剖析垃圾的过程中援用会变动,这样剖析的后果会不精确
根节点枚举
枚举GC Roots的过程是耗时的,因为要找到栈上的Reference作为根节点,这就不得不对整个栈进行扫描
为了防止枚举根节点消耗太多工夫,应用OopMap(Ordinary Object Pointer一般对象指针)数据结构来记录reference援用地位
根节点枚举必须暂停用户线程,因为要保障一致性的快照(根节点枚举是用户线程进展的重要起因)
如果单纯遍历GC Roots和援用链过程会十分的耗时,应用OopMap记录援用所在位置,扫描时不必去办法区全副扫描
应用OopMap疾速,精准的让HotSpot实现根节点枚举
平安点与平安区域
safe point
代码中援用的地位可能产生变动,每时每刻更新OopMap的开销是十分大的,因而规定在平安点地位才更新OopMap
那什么地位才是平安点呢?
平安点所在的地位个别具备让程序长时间执行的特色,比方办法调用、循环、异样跳转等
因为只有平安点地位的OopMap是无效的,因而在进行GC时用户线程须要停留在平安点
让用户线程到最近的平安点停下来的形式有两种,别离是领先式中断、主动式中断
领先式中断: 垃圾收集产生时,中断所有用户线程,如果有用户线程没在平安点上就复原它再让它执行会到平安点上
主动式中断: 设置一个标记位,当要产生垃圾回收时,就把这个标记位设置为真,用户线程执行时会被动轮询查看这个标记位,一旦发现它为真就去最近的平安点中断挂起
hotspot抉择主动式中断,应用内存保护陷阱形式将轮循标记位实现的只有一条汇编指令,高效
平安点设立太多会影响性能,设立太少可能会导致GC等待时间太长
平安点保障程序线程执行时,在不长时间内就可能进入垃圾收集过程的平安点
safe region
平安点只能保障程序线程执行时,在不长时间内进入平安点,如果是Sleep或者Blocking的线程呢?
平安区域:确保某一段代码中,援用关系不发生变化,这段区域中任意中央开始垃圾收集都是平安的
sleep、blocking线程须要停留在安全区能力进行GC
用户线程执行到安全区,会标识本人进入安全区,垃圾回收时就不会去管这些标识进入安全区的线程
用户线程要来到安全区时,会去查看是否执行完根节点枚举,执行完了就能够来到,没执行完就期待,直到收到能够来到的信号(枚举完GC Roots)
记忆集与卡表
后面说到过分代收集的概念,比方GC可能是只针对年老代的,但年老代对象可能援用老年代,对了可达性剖析的正确性可能要将老年代也退出GC Roots的扫描范畴中,这无疑又减少了一笔开销
上述问题叫做跨代援用问题,跨代援用问题不仅仅只存在与年老代与老年大中,相熟G1、低提早ZGC、Shenandoah收集器的同学会晓得它们分区region也会存在这种跨代援用
应用记忆集来记录存在跨代援用的状况,当产生跨代援用时只须要将一部分跨代援用的退出GC Roots的扫描范畴,而不必全副扫描
能够把记忆集看成记录从非收集区指向收集区的指针汇合
罕用卡表实现记忆集的卡精度(每个记录准确到内存区,该区域有对象有跨代指针)
卡表简略模式是一个字节数组,数组中每个元素对应着其标识内存区域中一块特定大小的内存区(这块内存区叫:卡页)
如果卡页上有对象含有跨代指针,就把对应卡表数组值改为1(卡表变脏),1阐明卡表对应的内存块有跨代指针,把卡表数组上元素为1的内存块退出GC Roots中一起扫描(图中卡表绿色地位示意卡表变脏存在跨代援用)
记忆集解决跨代援用问题,缩减GC Roots扫描范畴
写屏障
保护卡表变脏应该放在跨代援用赋值之后,应用写屏障来在跨代援用赋值操作后进行更新卡表
这里的写屏障能够了解为AOP,在赋值实现后进行更新卡表的状态
更新卡表操作产生额定的开销,在高并发状况下还可能产生伪共享问题,升高性能
能够不采纳无条件的写屏障,先查看卡表标记,只有未被标记过期才将其标记为变脏,来防止伪共享问题,但会减少额定判断的开销
-XX:+UseCondCardMark 是否开启卡表更新条件判断,开启减少额定判断的开销,能够防止伪共享问题
总结
本篇文章围绕垃圾回收算法细节深入浅出解析STW、根节点枚举防止长时间STW、安全区与平安区域、记忆集解决跨代援用增大GC Root扫描范畴、保护卡表的写屏障等
为了防止用户线程扭转援用关系,可能正确的进行可达性剖析,须要stop the word 进行用户线程
枚举GC Roots时为了防止长时间的STW,应用OopMap记录援用地位,防止扫描办法区
因为援用关系的变动,实时更新保护OopMap的开销是很大的,只有在循环、异样跳转、办法调用地位的平安点才更新OopMap,因而只有在平安点中能力正确的进行GC
安全区能够看成扩大的平安点,在一块代码中不会扭转援用关系;对于sleep、blocking状态的用户线程来说,只须要在安全区就可能进行GC
hotspot采纳主动轮循式中断,用户线程运行时主动轮循判断是否须要进行GC,须要进行GC则到左近最近的平安点/区,GC时不会治理这些进入安全区的用户线程,当用户线程要来到安全区时查看是否枚举完GC Root,枚举完则能够来到否则期待
跨代援用可能减少GC Root扫描范畴,应用卡表实现记忆集治理跨代援用,当卡表中的卡页变脏时阐明那块内存存在跨代援用,须要退出扫描范畴;记忆集无效缩小了扫描范畴
应用相似AOP的写屏障保护卡表状态,高并发状况下可能呈现伪共享问题,能够开启减少额定条件判断再进行保护卡表状态,减少条件判断开销但能够防止伪共享问题
最初(一键三连求求拉~)
本篇文章将被支出JVM专栏,感觉不错感兴趣的同学能够珍藏专栏哟~
本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔\~
有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下\~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 公布!