当前位置: 首页 > 后端技术 > Java

jvm中GCRoots枚举的理解

时间:2023-04-01 14:01:08 Java

这里对GCRoots枚举的理解只是理论上的,全堆垃圾回收。它不包括现实中垃圾收集器的具体实现,即不包括分代和分区垃圾收集。其实还有一个跨代参照、跨地域参照的问题。什么是GC根?所有具有堆外引用的对象都是GCRoots。局部变量(在栈中),静态变量和常量(在方法区中),它们都在堆外,只要引用了对象,那么这个对象就是GCRoot,会被加入到GCRoots集合中.想象一下,这个世界上没有垃圾收集器,我想发明一个。我遇到的第一个问题是如何判断一个对象是否已经死亡。一种方法是从当前必须处于活动状态的对象开始。如果我能找到所有活着的对象,那么剩下的就都死了,逻辑就是这么简单。对于活着的对象,我更喜欢称其为有用的对象,因为这样更能表达它不应该被清理的特性。只要满足两个条件,就可以在jvm中找到所有有用的对象:1.栈,方法区等,非堆内存区,持有对象。所有的对象都存放在堆中,它们就像仓库里的工具,用了才有用。它在哪里使用?堆外的其他区域。2.第一种情况的对象都是堆中使用的对象。使用手段,它调用的对象,它调用的对象,它调用的对象......(套娃)。是一个调用链。GCRoots就是要实现第一个条件,就是找到堆外区域引用的所有对象。Reachability分析就是要达到第二个条件,即找到所有被GCRoots使用过的对象。GCRoots枚举进程停止worldGCRoots枚举,需要暂停所有用户线程。原因很容易理解:GCRoots枚举时,统计的是堆外引用的对象。就以栈为例,如果客户端线程还在运行,那么在统计过程中,栈帧会不断的出栈和入栈。新添加的栈帧及其局部变量可能不会被计入GCRoots。(看完OopMap的机制,更新细节)safepoint可以随时挂起用户线程。为什么要设置安全点?要理解这个问题,你可能需要先了解OopMap,但是我的理解还不够透彻,所以只能泛泛而谈。用户线程挂起后,接下来会进行GCRoots枚举,但是GCRoots枚举依赖于OopMap中保存的信息。但是OppMap不可能每时每刻都更新数据,那样的话,资源的消耗就太大了。所以OopMap要更新数据,要找到一个节点,这个节点就是安全点。选择安全点时,只选择循环、调用和递归。..为什么是这样?JVM在挂起用户线程的时候,使用的是ActivePause,所以下面会和ActivePause一起讨论。当“stoptheworld”发生时,线程应该尽快运行到安全点,然后停止。如果在循环语句之后选择安全点,那就太傻了,白白增加停顿时间。所以肯定要在循环语句运行前选择,基本上是在这个循环语句中。综上所述,安全点的选择会在长时间运行的地方,比如循环、调用等,并且在这些代码运行之前,将线程挂起,避免浪费时间。saferegionsaferegion是一个安全点补丁。如果,当“stoptheworld”发生时,一些用户线程没有运行,比如sleep。那么,当“stoptheworld”发生时,这个线程并没有运行,所以它不能自己跑到安全点去中断线程。而是已经停在了一个非安全点,一旦获取到时间片,它仍然会运行。可能在下一个致命时期,也就是枚举GCRoots的时候,如果这个线程再次运行。那么这个漏洞就太大了。所以引入了安全区来弥补这个漏洞。当用户线程执行到安全区的代码时,会先标记自己已经进入安全区,这样虚拟机在这段时间要发起垃圾回收时,就不用关心这些已经进入安全区的线程了宣布自己在安全区。.当线程即将离开安全区时,会检查虚拟机是否完成了对根节点的枚举(或者垃圾回收过程中其他需要挂起用户线程的阶段)。如果它完成了,线程将继续运行,就好像什么都没发生过一样。执行;否则它必须等到它收到可以离开安全区域的信号。——摘自《深入理解java虚拟机》第三版总结GCRootsbeforeenumeration。首先,挂起用户线程,会使用安全点和安全区来辅助挂起用户线程。然后,开始GCRoots枚举,不再遍历方法区和栈中的引用,直接从OopMap中获取引用。