共计 1673 个字符,预计需要花费 5 分钟才能阅读完成。
对 GC Roots 枚举的了解
啥是 GC Roots?
但凡有堆外援用的对象,都是 GC Root。
局部变量(栈里)、动态变量和常量(办法区里),它们都是堆外的,只有援用了对象,那这个对象就是 GC Root,就会被退出 GC Roots 汇合。
构想,这个世界上还没有垃圾收集器,而我想要创造一个,那遇到的第一个问题就是,我该如何判断一个对象是否曾经死了。有一个办法,就是从以后必定活着的对象动手。如果,我能把所有活着的对象找齐了,那剩下的就是死的了,逻辑上就是这么简略。对于活对象,我更违心称它们为有用的对象,因为这更能表白它不应该被清理的特色。
只有两个条件,就能够找到 jvm 里所有,有用的对象:
1、栈、办法区等,非堆的内存区域,持有的对象。
堆里存了所有的对象,它们就像仓库里的工具,只有被应用才是有用的。被哪里应用呢?被堆外的其余区域。
2、第一个条件里的对象,在 堆内
应用的所有对象。
应用的意思是,它调用的对象,所调用的对象,所调用的对象 …(套娃中)。是一个调用链。
GC Roots 就是为了实现第一个条件,它要找到所有被堆外区域援用的对象。
可达性剖析,就是为了实现第二个条件,它要找到 GC Roots 所应用的所有对象。
GC Roots 枚举的过程
stop the world
GC Roots 枚举,必然须要暂停所有用户线程。
起因很好了解:GC Roots 枚举时,统计的就是 堆外
援用的对象。只拿栈中来说,如果客户线程仍旧在运行,那么统计过程中,一直有栈帧出栈和入栈。新入栈的栈帧,其中的局部变量,也有可能是不会被统计到 GC Roots 里。(看完 OopMap 的机制,再更新具体内容)
safe point
在任何时候,都能够暂停用户线程。为啥要设置 safe point?
了解这个问题,可能须要先搞懂 OopMap
,然而,我的了解临时还不透彻,所以只能抽象的讲。
用户线程暂停后,接下来会进行 GC Roots 枚举,而 GC Roots 枚举,却依赖 OopMap 存储的信息。然而,OppMap 不能每时每刻都更新数据,因为那样的话,对资源的耗费就太大了。
所以,要给 OopMap 更新数据,找一个节点,这个节点就是 safe point。
在选取 safe point 时,只会抉择在 循环
、 调用
、 递归
。。。这是为什么呢?
JVM 暂停用户线程的时候,都是采纳 主动式暂停
,所以以下就用 主动式暂停
来探讨。当 “stop the world” 产生时,线程应该尽快跑到平安点,而后停下来。如果在选取平安点的时候,选在了 循环语句
的前面,那就太蠢了,白白减少了进展工夫。所以,必定应该选在 循环语句
运行之前,根本就是在这个 循环语句
的中央。
总结,就是平安点的抉择,会在长时间运行的中央,比方 循环
、 调用
等中央。并在这些代码运行之前,暂停线程,避免浪费工夫。
safe region
safe region 是 safe point 的补丁。
如果,在 “stop the world” 产生时,有些用户线程没有运行,比方 sleep 了。那么,这条线程,在 “stop the world” 产生时,它不是运行状态,所以它并不能自行跑到平安点,并中断线程。而是,始终停在了非平安点的地位,一旦获取了工夫片,它还是要运行的。
可能会在,下一个致命的期间,也就是 GC Roots 枚举时,这条线程如果又跑起来了。
那这个破绽就太大了。
所以,又引入了 safe region,来补这个破绽。
当用户线程执行到平安区域外面的代码时, 首先会标识本人曾经进入了平安区域, 那样当这段时间里虚拟机要发动垃圾收集时就不用去管这些已申明本人在安区域内的线程了。
当线程要来到平安区域时, 它要查看虚拟机是否曾经实现了根节点枚举(或者垃圾收集过程中其余须要暂停用户线程的阶段), 如果实现了, 那线程就当作没事产生过, 继续执行; 否则它就必须始终期待, 直到收到能够来到平安区域的信号为止。
——摘抄自《深刻了解 java 虚拟机》第三版
总结
GC Roots 枚举前。首先,要暂停用户线程,会应用 safe point 和 safe ragion,辅助暂停用户线程。而后,开始 GC Roots 枚举,并不会去遍历办法区、栈里的援用,而是,间接从 OopMap 里获取援用。