【问题标题】:How GC finds GC roots and other object referencesGC 如何找到 GC 根和其他对象引用
【发布时间】:2021-02-28 08:07:23
【问题描述】:

根据这篇文章How Garbage Collection Works有四种Gc根:

  • 局部变量由线程堆栈保持活动状态。这不是真实的对象虚拟参考,因此不可见。为了 所有意图和目的,局部变量都是 GC 根。
  • 活动 Java 线程始终被视为活动对象,因此是 GC 根。这对于线程本地尤其重要
    变量。
  • 静态变量由它们的类引用。这一事实使它们成为事实上的 GC 根源。类本身可以被垃圾收集,这将删除所有引用的静态变量。当我们通常使用应用程序服务器、OSGi 容器或类加载器时,这一点特别重要。我们将在问题模式部分讨论相关问题。
  • JNI 引用是本机代码作为 JNI 调用的一部分创建的 Java 对象。这样创建的对象会被特殊处理,因为 JVM 不知道它是否被本机代码引用。此类对象代表了一种非常特殊的 GC 根形式,我们将在下面的问题模式部分进行更详细的研究。

在 JVM 规范中,堆栈帧中的局部变量没有类型,它只是一个字节数组,编译器负责为这些局部变量生成类型特定的指令,例如 iload、fload、aload 等。所以显然 GC 无法通过仅查看堆栈帧的局部变量部分来找到对对象的引用。

我的问题是:

  1. GC 是如何找到这些根的?

  2. GC 如何在堆栈中找到引用的局部变量 object 而不是其他类型的变量(例如iconst存储的实例变量)?

  3. 那么 Gc 如何找到这些对象的字段以创建可访问的 树?

  4. 它是否使用 JVM 自己定义的指令来查找那些 对象?

  5. 最后这句话在文章中是什么意思?

这不是真实对象的虚拟引用,因此不可见

【问题讨论】:

    标签: java garbage-collection jvm


    【解决方案1】:
    1. GC 究竟是如何找到这些根的?

    JVM 提供了一个用于查找根的内部 (C/C++) API。 JVM 知道 Java 堆栈帧在哪里,每个类的 Java 静态帧在哪里,活动的 JNI 对象句柄在哪里,等等。 (它知道是因为它参与了创建它们并跟踪它们。)

    1. GC 如何在堆栈中找到作为对象引用而不是其他类型变量的局部变量。

    JVM 保存每个方法的信息,说明每个堆栈帧中的哪些单元是引用变量。 GC 可以算出每个栈帧对应的方法……就像fillInStackTrace 可以一样。

    (例如常量)

    这实际上并不相关。常量(即final 字段)没有得到特殊处理。

    1. 那么 GC 如何找到这些对象的字段来创建可访问的树?

    JVM 保存每个类的信息,说明哪些静态和实例字段是引用变量。每个对象的标题中都有一个引用该类的字段。

    整个过程称为“标记”,在您正在查看的页面中进行了描述。

    1. 它是否使用 jvm 自身定义的指令来查找这些对象?

    我不确定你在问什么。但是“可能是的”。 GC是JVM的一个组件,所以所做的一切都是“由JVM定义的”。

    1. 最后这句话在文章中是什么意思?

    这不是真实对象的虚拟引用,因此不可见

    这可能是说线程的堆栈不是 Java 对象……这是真的。但我认为您需要询问该电子书的作者;他们的名字见https://www.dynatrace.com/resources/ebooks/javabook/的底部。


    你添加了这个:

    在 JVM 规范中,堆栈帧中的局部变量没有类型,它只是一个字节数组,编译器负责为这些局部变量生成类型特定的指令,例如 iload、fload、aload 等。所以显然 GC 无法通过仅查看堆栈帧的局部变量部分来找到对对象的引用。

    实际上,这不是真的。正如@Holder 提醒我的那样,验证器通过模拟初始化它们的字节码的效果来推断堆栈帧中单元的类型。此外,类文件中的每个方法都有一个StackMapTable 属性,其中包含用于帮助(和加速)验证器类型确定的信息。

    稍后,GC 可以从 JVM 中获取推断的类型信息。

    (理论上,GC 也可以利用StackMapTable 信息来确定局部变量何时超出范围...... 在方法中。但显然它没有在 HotSpot JVM 中;参见 Does the StackMapTable affect the garbage collection behavior?)


    该电子书中对垃圾收集的描述(故意)简短而高级。但是对于您会发现的大多数描述都是如此。深层细节很复杂。

    如果您真的想(并且需要)了解 GC 的工作原理,我的建议是:

    • 要了解当前 Java 实现的工作原理阅读 OpenJDK 源代码。
    • 查找并阅读 Sun 和 Oracle 关于 Java GC 的研究论文。
    • 获取一本关于垃圾收集的好教科书。

    【讨论】:

    • 感谢您的出色回答,顺便说一句,Constant 我的意思是通过 iconst 等指令存储的局部变量(不是 Java 中的 final 关键字)。我编辑了我的问题以反映这一点
    • 这是不正确的。类文件中有更多信息。请参阅我对答案的编辑。就像我说的,“深层细节很复杂”......如果你想完全理解它,你需要比字节码指令集更深入。
    • 这个说法有点误导。因为 JVM 规范的其他部分谈到了验证。但是,我认为它的意思是 values 不需要标记。这是指其他一些语言实现(在某些情况下是硬件),其中值有一个标签位来区分指针值和原始值。 (例如:LISP 机器和 1980 年代的 MIT CLU 编译器。)
    • No ..... 你应该得出的结论是值不使用/需要标签位来区分它们的类型。因为 JVM 和 GC 可以从上下文中计算出值的类型。 Java 中的值总是有明确的类型。
    • 小修正:堆栈值的类型由推理确定,使用已知的初始状态并建模每条指令的效果,如also discussed here。 StackMapTable 属性仅描述分支合并点,以帮助验证者。在这些点之间,验证器使用推理,因为它用于 Java 6 之前的整个方法。虽然 GC 可以从这些信息中受益,但它实际上并没有在广泛的 Hotspot JVM 中使用,如this question
    猜你喜欢
    • 2018-03-21
    • 2018-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多