【问题标题】:Garbage collection - root nodes垃圾收集 - 根节点
【发布时间】:2012-01-18 21:02:58
【问题描述】:

我最近阅读了一些关于垃圾收集的零碎文章(主要是在 Java 中),但仍有一个问题没有得到解答:JVM(或一般的运行时系统)如何跟踪当前活动的对象?

我知道对象是当前在堆栈上的对象,因此所有局部变量或函数参数都是对象。这种方法的问题是,每当运行时系统检查当前堆栈上的内容时,它如何区分引用变量和简单的 int?不能,可以吗?

因此,必须有某种机制来允许运行时构建活动对象的初始列表以传递给标记-扫描阶段...

【问题讨论】:

    标签: garbage-collection heap-memory stack-memory


    【解决方案1】:

    HotSpot VM 为每个编译的子例程生成一个 GC 映射,其中包含有关根所在位置的信息。例如,假设它编译了一个120字节长的机器码(字节码原理相同)的子程序,那么它的GC映射可能是这样的:

    0 : [RAX, RBX]
    4 : [RAX, [RSP+0]]
    10 : [RBX, RSI, [RSP+0]]
    ...
    120 : [[RSP+0],[RSP+8]]
    

    这里[RSP+x] 应该表示堆栈位置和R?? 寄存器。因此,如果线程在偏移 10 处的汇编指令处停止并运行 gc 循环,则 HotSpot 知道三个根位于 RBXRSI[RSP+0]。如果必须移动对象,它会跟踪这些根并更新指针。

    我为 GC 图描述的格式只是为了演示原理,显然不是 HotSpot 实际使用的格式。它不完整,因为它不包含有关包含原始实时值的寄存器和堆栈槽的信息,并且为每个指令偏移量使用列表并不节省空间。您可以通过多种方式以更有效的方式打包信息。

    【讨论】:

    • 这张地图只需要在安全点上而不是在任意偏移上(这可能是你在 0、4 和 10 之间的差距的原因)。我刚刚发现 this article 支持您的回答。
    【解决方案2】:

    我发现 grayfairer 提供的答案是错误的。 JVM 运行时不会通过查看用于将数据推送到堆栈上的字节码来从堆栈中收集根集。堆栈帧由 4 个字节(32 位拱形)插槽组成。每个槽可以是对堆对象或原始值(例如 int)的引用。当需要 GC 时,运行时会从上到下扫描堆栈。对于每个插槽,它都包含一个引用,如果:

    一个。它以 4 字节边界对齐。

    b.槽中的值指向堆的区域(在下限和上限之间)。

    c。分配位已设置。 allocbit 是一个标志,表示它对应的内存位置是否被分配。

    这是我的参考资料:http://www.ibm.com/developerworks/ibm/library/i-garbage2/

    还有一些其他技术可以找到根集(不是在 Java 中)。例如,因为指针通常在 4/8 字节边界对齐,所以第一位可用于指示槽是原始值还是指针:对于原始值,第一位设置为 1。这样做的缺点是你只有 31 位(32 位拱)来表示整数,并且对原始值的每个操作都涉及移位,这显然是一种开销。

    此外,您可以在堆上分配包括 int 在内的所有类型。也就是说,一切事物都是对象。然后堆栈帧中的所有插槽都是引用。

    【讨论】:

    • 所以总而言之,这是相当低级的差异化,而不是 JVM?但是 JVM 为字节码声明了一个引用类型,那么为什么不使用它呢?你确定是这么低级而不是字节码级别?
    • 据我所知(基于我之前给出的链接,以及浏览了几个JVM实现的代码),我相信我的理解是正确的。您可以简单地深入研究一些开源 JVM 实现的 GC 代码来检查这一点。他们都需要遍历堆栈以找出引用。但是,可能用于验证插槽是否为引用的标准略有不同(其中大多数验证a。和b。对于c,它实际上是基于实现的)。
    • 为什么不用字节码,这是我的理解(不知道对不对)。 GC 是运行时的东西,但字节码是在编译时生成的,并且是静态的。当 GC 发生时,运行时系统需要找出根并跟踪它们以找出活动对象。 .为此,您必须实际检查每个堆栈帧插槽中的值,即使您知道该插槽在编译时包含一个引用(正如 grayfairer 所说,您可以通过查看字节码知道这一点)。因为您需要知道确切的参考值才能在堆中查找其他对象。
    • 那么为什么还要检查字节码呢?无论如何,你必须走堆栈。
    • 分配位在哪里?在对象之外的某个地方,您会增加分配开销(仅通过一个操作,但这很重要)。在对象内部时,您可能会将其他数据误解为 allocbit 并遇到this article 底部提到的问题。
    【解决方案3】:

    运行时可以完美地区分引用变量和原语,因为那是在编译的字节码中。

    例如,如果函数 f1 调用函数 f2(int i, Object o, long l),则调用函数 f1 会将 4 个字节压入堆栈(或寄存器中),表示 i、4(或 8?) o 的引用字节,l 的 8 个字节。被调用的函数 f2 知道在堆栈上的哪里可以找到这些字节,并且可能会将引用复制到堆上的某个对象,或者不复制。当函数 f2 返回时,调用函数将从堆栈中删除参数。

    运行时解释字节码并记录它在堆栈上推送或丢弃的内容,因此它知道什么是引用,什么是原始值。

    根据http://www.javacoffeebreak.com/articles/thinkinginjava/abitaboutgarbagecollection.html,java 使用tracing garbage collector 而不是引用计数算法。

    【讨论】:

    • 感谢您的回答。考虑到这一点,当垃圾收集由 JVM 启动时,它是如何进行的?它实际上是如何定位根节点的 - 跳回堆栈还是有单独的节点集合?
    • 查看文章链接进行深入剖析。
    • 我在您提到的文章中找到了以下句子“标记和扫描遵循从堆栈和静态存储开始并跟踪所有句柄以查找活动对象的相同逻辑。”他们指的这些神秘的把手是什么……
    • 句柄、指针、引用,对我来说都一样。这意味着运行时确实在堆栈上保留了一个位置列表,这些位置是堆上对象的引用/指针,并从那里找到指向这些对象引用的其他对象的指针,依此类推......
    • 啊,好吧,那么辅助数据结构正在被使用...这有道理!
    猜你喜欢
    • 1970-01-01
    • 2012-01-10
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 2013-01-26
    • 2018-05-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多