【问题标题】:What are the roots?根源是什么?
【发布时间】:2015-11-05 19:59:04
【问题描述】:

垃圾回收的根源是什么?

我已将 root 的定义读为“您的程序可以访问的任何引用”,而 live 的定义是正在使用的对象,可以是局部变量、静态变量。

我对区分根对象和活动对象之间的区别有点困惑。

什么是根路径?根对象和活动对象如何工作?

有人可以详细说明吗?

【问题讨论】:

  • 什么糟糕的定义:) 我将从Garbage Collection开始
  • @user177833 - 你在哪里读到这些定义?
  • 该页面中对根的定义是:“您的程序可以直接访问的任何对象引用,无需通过其他对象”。这与“您编程可以访问的任何参考”大不相同。这是非常具体的,因为您的程序持有对所述托管对象的引用,并且您的程序不需要遍历堆到达根。
  • 您需要将 JVM/CLR 可视化为管理堆的实际进程。进程知道的唯一对象是正在执行的线程堆栈帧集、已加载的类以及其他一些对象。这是 GC 根;堆中的所有其他对象都可以从此集合中访问或无法访问。

标签: .net garbage-collection jvm clr gc-roots


【解决方案1】:

根或垃圾回收根是始终可访问的对象。如果一个对象总是可达的,那么它就没有资格进行垃圾回收;因此,根始终没有资格收集。它是确定堆上所有其他对象的可达性的初始对象集。

堆上从垃圾收集根可到达的其他对象被认为是活动对象,并且没有资格被收集;可以将无法访问的对象标记为回收。

我比 .Net 平台更了解 Java,所以我只说一个。在 Java 平台上,GC 根实际上是依赖于实现的。然而,在大多数运行时,GC 根往往是堆栈上的操作数(因为它们当前正被线程使用)和类的类(静态)成员。在大多数 JVM 中,可达性是根据这些对象计算的。在其他情况下,JNI 调用使用的本地参数和操作数将被视为根集的一部分,也用于计算可达性。

我希望这可以消除关于什么是根(集合)和什么是活动对象的任何挥之不去的疑问。

【讨论】:

  • 我可以说根是指向活动对象的指针吗?如果没有从根到对象的路径,那么垃圾回收可以声明该对象?
  • 根是活动对象。不要将指针带入其中并让自己感到困惑(GC 算法使用对对象的引用数来确定可达性;通过将根视为指针来查看您在那里做了什么)。必须使用指针/引用来确定可达性。
  • 上面的注释应该读作“根是 JVM/CLR 已知的活动对象”。将它们视为指针的问题在于 GC 算法将更加复杂,因为任何 GC 算法都处理对象的指针/引用的数量以区分活动对象和可收集对象。一旦根是指针,所有根指针(原文如此)都必须以不同方式处理,没有明显的好处。
  • @VineetReynolds “GC 根往往是堆栈上的操作数(因为它们当前正被线程使用)”您所说的“堆栈上的操作数”是什么意思?
  • @Geek,方法的局部变量,它的参数等。
【解决方案2】:

在java中我会说线程是根对象。每个活动对象都可以追溯到活动线程。例如,一个静态对象被一个类引用,该类被一个类加载器引用,该类被另一个类引用,该类的一个实例引用该类,......它被一个Runnable引用,它被引用通过实时线程。 (注意,类可以被 GC,它们不能是根

我们也可以为所有线程考虑一个“真正的”根,但这超出了标准 Java 的范围。我们不能说它是什么,以及它是如何引用所有线程的。

【讨论】:

  • 加载的类也是根类(因为它们可能包含全局/静态变量)。
  • 只有在加载的类变得无法访问时,才能对类进行 GC;系统加载器加载的类不能被 GC。
【解决方案3】:

如果您将内存中的对象视为一棵树,那么“根”将是根节点 - 您的程序可以立即访问的每个对象。

Person p = new Person();
p.car = new Car(RED);
p.car.engine = new Engine();
p.car.horn = new AnnoyingHorn();

有四个对象;一个人,一辆红色的汽车,它的引擎和喇叭。绘制参考图:

     Person [p]
        |
     Car (red)
   /           \
Engine    AnnoyingHorn

您最终会在树的“根”处找到Person。它是实时的,因为它被一个局部变量 p 引用,程序可能随时使用它来引用 Person 对象。这也适用于其他对象,通过p.carp.car.engine 等。

由于Person 和所有其他递归连接到它的对象都是活动的,如果 GC 收集它们会很麻烦。

但是,请考虑,如果在一段时间后运行以下命令:

p.car = new Car(BLUE);

并重新绘制图形:

     Person [p]
        |
     Car (blue)       Car (red)
                    /           \
                Engine    AnnoyingHorn

现在Person 可通过p 访问,蓝色汽车可通过p.car 访问,但红色汽车或其部件永远无法再次访问 - 它们未连接到活动根。可以安全地收集它们。

因此,这实际上是获取每个起点(每个局部变量、全局变量、静态变量、其他线程和堆栈帧中的所有内容)——每个根——并递归地跟踪所有引用以组成所有“实时”列表的问题" 对象:正在使用且不适合删除的对象。其他都是垃圾,等待收集。

【讨论】:

  • 这个答案不正确。 GC Roots 是 JVM 根据 [Veneet 的回答] 加载的那些类,特别是线程、系统类加载器加载的类、堆栈中的引用、JNI 和等待终结的对象。
  • 请参阅this other answer 以获取可能的根列表。在这个答案中,Person 不是根,它可以被一个(并且很可能不止一个)根访问。
  • 因为这个问题没有在任何地方指定 Java 或 JVM(除了标签,如果你看起来足够近,它们还包含 .NET 和 CLR)并且似乎相当通用,所以我的回答也是如此。感谢您对 Java 部分的澄清,但我看不出它如何使我的通用答案无效。
  • 在您的具体示例中,在任何托管环境中,Person不是 GC 根; GC 根是 thing,它包含对 Person 的引用。差异是微妙的,但在这个问题的背景下很重要。尽管我的回答是针对 Java 的,但它通常对任何托管语言都是正确的。您的最后一段实际上是正确的,但与给出的示例冲突。
  • 我仍然喜欢这个答案,因为它有助于阐明 GC“一般”是如何工作的。
【解决方案4】:

GC(垃圾收集器)根是垃圾收集器专用的对象。垃圾收集器收集那些不是 GC 根并且不能通过 GC 根的引用访问的对象。

有几种 GC 根。一个对象可以属于一种以上的根。根类是:

  • 类 - 由系统类加载器加载的类。这样的类永远无法卸载。它们可以通过静态字段保存对象。请注意,自定义类加载器加载的类不是根,除非 java.lang.Class 的相应实例恰好是其他类型的根。
  • 线程 - 实时线程
  • Stack Local - Java 方法的局部变量或参数
  • JNI Local - JNI 方法的局部变量或参数
  • JNI Global - 全局 JNI 参考
  • 使用的监视器 - 用作同步监视器的对象
  • 由 JVM 持有 - JVM 出于其目的从垃圾收集中持有的对象。实际上,此类对象的列表取决于 JVM 实现。可能的已知情况有:系统类加载器、JVM 知道的一些重要的异常类、一些用于异常处理的预分配对象以及在加载类的过程中的自定义类加载器。不幸的是,JVM 完全没有为这些对象提供额外的细节。因此,由分析人员决定某个“由 JVM 持有”属于哪种情况。

(感谢YourKit's website

YourKit 没有提到的事实是等待终结的对象将被保留为根,直到 GC 运行 finalize() 方法。这可能会意外地导致大图的暂时保留。一般的经验法则是不要使用终结器(但这是一个不同的问题)。

【讨论】:

  • 您可以考虑采购这个复制/粘贴的答案:yourkit.com/docs/12/help/gc_roots.jsp,或者 yourkit 可能会考虑采购您:-)。
  • 未提及等待终结的对象,JVM 在终结运行之前持有对这些对象的引用。
  • 有时引用先存储在操作数栈中,然后再存储到局部变量表中。 GC 是如何解决这个问题的?
【解决方案5】:

IBM web site 将以下内容列为 GC 根。

请注意,其中一些是由内存分析器完成的人为构造,但如果您正在查看堆转储,请注意这一点。

  • 系统类

    由引导加载程序或系统类加载程序加载的类。例如,该类别包括 rt.jar 文件(Java 运行时环境的一部分)中的所有类,例如 java.util.* 包中的类。

  • JNI 本地

    本地代码中的局部变量,例如用户定义的 JNI 代码或 JVM 内部代码。

  • JNI 全局

    本地代码中的全局变量,例如用户定义的 JNI 代码或 JVM 内部代码。

  • 线程块

    从活动线程块引用的对象。

  • 线程

    一个正在运行的线程。

  • 忙碌的监视器

    所有调用 wait() 或 notify() 方法,或同步的,例如通过调用 synchronized(Object) 方法或输入同步方法。如果方法是静态的,则根是一个类,否则它是一个对象。

  • Java 本地

    一个局部变量。例如,输入参数或仍在线程堆栈中的方法的本地创建对象。 原生栈

    本机代码中的输入或输出参数,例如用户定义的 JNI 代码或 JVM 内部代码。许多方法都有本机部分,作为方法参数处理的对象成为垃圾收集根。例如,用于文件、网络、I/O 或反射操作的参数。

  • 终结器

    队列中的对象,等待终结器运行。

  • 未定稿

    一个对象有一个 finalize 方法,但还没有完成,并且还没有在 finalizer 队列中。

  • 无法访问

    无法从任何其他根访问的对象,但已被内存分析器标记为根,以便可以将对象包含在分析中。

    无法访问的对象通常是垃圾收集算法优化的结果。例如,一个对象可能是垃圾收集的候选对象,但它太小以至于垃圾收集过程太昂贵。在这种情况下,该对象可能不会被垃圾回收,并且可能仍然是一个无法访问的对象。

    默认情况下,内存分析器解析堆转储时会排除无法访问的对象。因此,这些对象不会显示在直方图、支配树或查询结果中。您可以通过单击 File > Preferences... > IBM Diagnostic Tools for Java - Memory Analyzer,然后选中 Keep unreachable objects 复选框来更改此行为。

  • Java 堆栈帧

    一个 Java 堆栈框架,其中包含局部变量。仅当您将首选项设置为将 Java 堆栈帧视为对象时,才会生成这种类型的垃圾收集根。有关详细信息,请参阅 Java 基础知识:线程和线程堆栈查询。

  • 未知

    未知根类型的对象。某些转储(例如 IBM 便携式堆转储 (.phd) 文件)没有根信息。在这种情况下,内存分析器解析器将没有入站引用或无法从任何其他根访问的对象标记为未知。此操作可确保 Memory Analyzer 保留转储中的所有对象。

【讨论】: