关于.net:根源是什么?

What are the roots?

垃圾收集的根源是什么?

我已经将根的定义理解为"任何程序可以访问的引用",而live的定义是正在使用的对象,它可以是局部变量、静态变量。

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

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

有人能详细解释一下吗?


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

1
2
3
4
Person p = new Person();
p.car = new Car(RED);
p.car.engine = new Engine();
p.car.horn = new AnnoyingHorn();

有四个物体:一个人,一辆红色汽车,发动机和喇叭。绘制参考图:

1
2
3
4
5
     Person [p]
        |
     Car (red)
   /           \
Engine    AnnoyingHorn

最后你会发现在树的"根"上有一个Person。它是活动的,因为它被局部变量p引用,程序可以在任何时候使用它来引用Person对象。这也适用于其他对象,通过p.carp.car.engine等。

由于Person和递归连接到它的所有其他对象都是活动的,所以如果GC收集到它们,就会出现问题。

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

1
p.car = new Car(BLUE);

重新绘制图表:

1
2
3
4
5
     Person [p]
        |
     Car (blue)       Car (red)
                    /           \
                Engine    AnnoyingHorn

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

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


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

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

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

(登录yourkit网站)

Yourkit没有提到等待完成的对象将作为根保留,直到GC运行finalize()方法。这可能会导致大图的暂时保留,有些出乎意料。一般的经验法则是不使用终结器(但这是一个不同的问题)。


根或垃圾收集根是始终可以访问的对象。如果一个对象总是可以访问的,那么它就不符合垃圾收集的条件;因此,根总是不符合收集的条件。它是一组初始对象,从中可以确定堆上所有其他对象的可访问性。

堆上可从垃圾收集根访问的其他对象被认为是活动对象,不适合收集;无法访问的对象可以标记为回收。

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

我希望这能消除人们对什么是根(集)和什么是活的物体的疑虑。


IBM网站将以下内容列为GC根。

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

  • 系统类

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

  • 本地JNI

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

  • JNI全球

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

  • 线程块

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

  • 螺纹

    正在运行的线程。

  • 占线监视器

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

  • 爪哇本地

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

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

  • 终结器

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

  • 未完成的

    具有Finalize方法但尚未完成的对象,该对象尚未在终结器队列中。

  • 不可达

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

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

    默认情况下,当内存分析器解析堆转储时,不可访问的对象被排除在外。因此,这些对象不会显示在柱状图、主宰树或查询结果中。您可以通过单击文件>首选项来更改此行为…>用于Java的内存诊断工具-内存分析器,然后选择"保持不可达对象"复选框。

  • Java堆栈框架

    一个Java堆栈框架,它保存局部变量。只有设置首选项将Java堆栈帧作为对象来处理时,才会生成此类垃圾回收根。有关更多信息,请参见Java基础知识:线程和线程堆栈查询。

  • 未知

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


在Java中,我会说线程是根对象。每个活动对象都可以追溯到活动线程。例如,静态对象由类引用,类由类加载器引用,类加载器由另一个类引用,该类的实例引用,…它由可运行的引用,由活动线程引用。(注意,类可以是gc'ed,不能是根)

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