乐趣区

关于后端:深入浅出JVM十三之垃圾回收算法细节

上篇文章深入浅出 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 公布!

退出移动版