为什么非静态字段不作为GC根? [英] Why does a non-static field not act as a GC root?

查看:131
本文介绍了为什么非静态字段不作为GC根?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道静态字段(与线程,本地变量和方法参数,JNI引用一起)充当GC根。



我无法提供确认这个,但我已经阅读了很多文章。



为什么非静态字段不能充当GC根?

$ b $首先,我们需要确定我们与追踪垃圾收集算法在其标记阶段执行的操作相同。

>

在任何特定时刻,跟踪GC都有一些已知活着的对象,因为它们现在可以通过正在运行的程序来访问。标记短语的主要步骤包括跟踪这些对象的非静态字段以查找更多对象,并且现在还知道这些新对象是活动的。递归地重复此步骤,直到没有新的活动通过遍历现有的活动对象来找到对象。记忆体中没有被证实的所有物体都被认为是死的。 (然后GC进入下一阶段,这就是所谓的扫描阶段,我们不关心这个答案的阶段。)



现在,这本身并不是足以执行该算法。在开始时,算法没有任何它知道活着的对象,所以它不能跟随任何人的非静态字段。我们需要指定一组从一开始就被认为是活着的对象。我们从公理上选择这些对象,因为它们不是来自算法的前一步 - 它们来自外部。具体来说,它们来自语言的语义。这些对象称为根。



在像Java这样的语言中,有两组对象是明确的GC根。任何可以通过仍然在范围内的局部变量访问的东西显然是可访问的(在它的方法中,仍然没有返回),因此它是活着的,因此它是一个根。任何可以通过类的静态字段访问的东西也显然可以从任何地方访问,因此它是活的,因此它是一个根。



但是,如果非静态字段也被认为是根源,会发生什么?假设你实例化一个 ArrayList< E> 。在内部,该对象有一个非静态字段,指向一个 Object [] (表示存储列表的支持数组)。在某个时候,GC循环开始。在标记阶段, Object [] 被标记为存活,因为它是由 ArrayList< E> 私人非静态字段。 ArrayList< E> 没有被任何东西指向,所以它不被认为是活着的。因此,在这个循环中, ArrayList< E> 被销毁,而支持 Object [] 存活。当然,在下一个循环中, Object [] 也会死掉,因为任何根目录都无法访问它。但为什么在两个周期中做到这一点?如果 ArrayList< E> 在第一个循环中死了,并且如果 Object [] 仅由死对象,为了节省时间和空间,不应该将 Object [] 视为同一动作中的死亡?

这就是要点。如果我们想要最高效(在跟踪GC中),我们需要在单个GC中尽可能多地清除死对象。



要那么,只有当封闭对象(包含该字段的对象)已被证明是活的时,非静态字段才应该保持对象有效。 相反,根是对象,我们为了启动算法的标记阶段,公理地调用(没有证明)。例如,假设你有这样的代码:

p>

  class Foo {
Bar bar = new Bar();

public static void main(String [] args){
Foo foo = new Foo();
System.gc();
}

public void test(){
Integer a = 1;
bar.counter ++; //访问非静态字段
}
}

class Bar {
int counter = 0;
}




  • 当垃圾收集开始时,根就是本地变量 Foo foo 。就是这样,那是我们唯一的根。

  • 我们按照根目录找到 Foo 的实例,它被标记为活动,然后我们试图找到它的非静态领域。我们找到其中的一个, Bar bar 字段。

  • 我们按照字段查找 Bar ,它被标记为活动,然后我们试图找到它的非静态字段。我们发现它不包含更多的引用类型的字段,所以GC不再需要为该对象而烦恼。

  • 由于我们无法在此找到新的活动对象



另外:

  class Foo {
Bar bar = new Bar();

public static void main(String [] args){
Foo foo = new Foo();
foo.test();
}

public void test(){
Integer a = 1;
bar.counter ++; //访问非静态字段
System.gc();
}
}

class Bar {
int counter = 0;
}




  • 当垃圾收集开始时,局部变量整数a 是一个根,并且 Foo this 引用(所有非静态方法获得的隐式引用)也是一个根。 main 中的局部变量 Foo foo 也是一个根,因为 main 尚未超出范围。

  • 我们追踪根目录来查找 Integer 的实例和 Foo (我们发现其中的一个对象有两次,但这对算法无关紧要),它们被标记为有效,然后我们试图遵循它们的非静态字段。假设 Integer 的实例没有更多类实例的字段。 Foo 的实例给我们一个 Bar 字段。

  • 字段来查找 Bar 的实例,该实例被标记为活动,然后我们试图找到它的非静态字段。我们发现它不包含更多的引用类型的字段,所以GC不再需要为该对象而烦恼。

  • 由于我们无法在此找到新的活动对象一轮递归,标记阶段可以结束。


As I know static fields (along with Threads, local variables and method arguments, JNI references) act as GC roots.

I cannot provide a link that would confirm this, but I have read a lot of articles on it.

Why can't a non-static field act as a GC root?

解决方案

First off, we need to be sure we're on the same page as to what a tracing garbage collection algorithm does in its mark phase.

At any given moment, a tracing GC has a number of objects that are known to be alive, in the sense that they are reachable by the running program as it stands right now. The main step of mark phrase involves following the non-static fields of those objects to find more objects, and those new objects will now also be known to be alive. This step is repeated recursively until no new alive objects are found by traversing the existing live objects. All objects in memory not proved live are considered dead. (The GC then moves to the next phase, which is called the sweep phase. We don't care about that phase for this answer.)

Now this alone is not enough to execute the algorithm. In the beginning, the algorithm has no objects that it knows to be alive, so it can't start following anyone's non-static fields. We need to specify a set of objects that are considered known to be alive from the start. We choose those objects axiomatically, in the sense that they don't come from a previous step of the algorithm -- they come from outside. Specifically, they come from the semantics of the language. Those objects are called roots.

In a language like Java, there are two sets of objects that are definite GC roots. Anything that is accessible by a local variable that's still in scope is obviously reachable (within its method, which still hasn't returned), therefore it's alive, therefore it's a root. Anything that is accessible through a static field of a class is also obviously reachable (from anywhere), therefore it's alive, therefore it's a root.

But if non-static fields were considered roots as well, what would happen?

Say you instantiate an ArrayList<E>. Inside, that object has a non-static field that points to an Object[] (the backing array that represents the storage of the list). At some point, a GC cycle starts. In the mark phase, the Object[] is marked as alive because it is pointed to by the ArrayList<E> private non-static field. The ArrayList<E> is not pointed to by anything, so it fails to be considered alive. Thus, in this cycle, the ArrayList<E> is destroyed while the backing Object[] survives. Of course, at the next cycle, the Object[] also dies, because it is not reachable by any root. But why do this in two cycles? If the ArrayList<E> was dead in the first cycle and if Object[] is used only by a dead object, shouldn't the Object[] also be considered dead in the same move, to save time and space?

That's the point here. If we want to be maximally efficient (in the context of a tracing GC), we need to get rid of as many dead objects as possible in a single GC.

To do that, a non-static field should keep an object alive only if the enclosing object (the object that contains the field) has been proved to be alive. By contrast, roots are objects we call alive axiomatically (without proof) in order to kick-start the algorithm's marking phase. It is in our best interest to limit the latter category to the bare minimum that doesn't break the running program.

For example, say you have this code:

class Foo {
    Bar bar = new Bar();

    public static void main(String[] args) {
        Foo foo = new Foo();
        System.gc();
    }

    public void test() {
        Integer a = 1;
        bar.counter++; //access to the non-static field
    }
}

class Bar {
    int counter = 0;
}

  • When the garbage collection starts, we get one root that's the local variable Foo foo. That's it, that's our only root.
  • We follow the root to find the instance of Foo, which is marked as alive and then we attempt to find its non-static fields. We find one of them, the Bar bar field.
  • We follow the fields to find the instance of Bar, which is marked as alive and then we attempt to find its non-static fields. We find that it contains no more fields that are reference types, so the GC doesn't need to bother for that object anymore.
  • Since we can't get find new alive objects in this round of recursion, the mark phase can end.

Alternatively:

class Foo {
    Bar bar = new Bar();

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.test();
    }

    public void test() {
        Integer a = 1;
        bar.counter++; //access to the non-static field
        System.gc();
    }
}

class Bar {
    int counter = 0;
}

  • When the garbage collection starts, the local variable Integer a is a root and the Foo this reference (the implicit reference that all non-static methods get) is also a root. The local variable Foo foo from main is also a root because main hasn't gone out of scope yet.
  • We follow the root to find the instance of Integer and instance of Foo (we find one of these objects twice, but this doesn't matter for the algorithm), which are marked as alive and then we attempt to follow their non-static fields. Let's say the instance of Integer has no more fields to class instances. The instance of Foo gives us one Bar field.
  • We follow the field to find the instance of Bar, which is marked as alive and then we attempt to find its non-static fields. We find that it contains no more fields that are reference types, so the GC doesn't need to bother for that object anymore.
  • Since we can't get find new alive objects in this round of recursion, the mark phase can end.

这篇关于为什么非静态字段不作为GC根?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆