【问题标题】:Object lifecycle and GC in case of Composition组合情况下的对象生命周期和 GC
【发布时间】:2021-05-23 07:23:27
【问题描述】:

以下是合成的示例:

public class A3 {
    B b = new B();
}

我了解到,一旦A3 的对象被 GC(或成为 GC 资格),B 的对象也将被 GC(或成为 GC 资格)。

现在,让我们考虑以下情况,因此根据我的理解,d.m1(); 之后,堆上的a3 对象将符合 GC 条件,但我认为 b 对象不符合 GC 条件。

我的两个理解是相互联系的,谁能帮我确定哪一个是错误的。

public class Testing {

    public static void main(String[] args) {
        D d = new D();
        d.m1();
        d.m2();
        // do some more things...
    }
}

    public class A3 {
        B b = new B();
        
        public B getB() {
            return b;
        }
    }
    
    public class B{
        public void m1() {
            System.out.println("B.m1");
        }
    }
    
    public class D{
        B bd2;
        
        public void m1() {
            A3 a3 = new A3();
            bd2 = a3.getB();
        }
        
        public void m2() {
            bd2.m1();
        }
    }

更新:我确实理解“有资格获得 GC”和“实际获得 GC'ed”之间的区别,所以为了方便起见,假设对象一旦符合条件就会立即被 GC'ed .

【问题讨论】:

标签: java memory-management garbage-collection


【解决方案1】:

@Shadab 和@Koenigsberg 说的是对的,这里有一些解释和演示 - 基本上我所做的是我已经覆盖了 finalize() 方法,以便向您展示对象是如何被 GC 处理的。

  • 这里要注意的关键是,只要方法的堆栈帧从堆栈中取出,任何在方法内部创建且未从该方法传递到任何其他位置的对象都将符合 GC 条件。这就是为什么您会在 d.m1(); 结束时看到“A3 已被垃圾回收”。
  • 出于同样的原因,一旦test1(); 结束,您就会看到“D 被垃圾回收”。如果您注释掉test2();,则不会打印“D 被垃圾回收”,因为一旦删除了test1 方法的堆栈帧,就没有(强制)GC,并且只要存在test1 的堆栈帧在堆栈上,对象d 无论如何都无法删除。
  • 对象 'b' 的 GC 有点棘手,如果你不让线程休眠几毫秒,那么它就不会被 GC'ed,遗憾的是我不知道对此的确切解释。李>

另一个需要注意的重点是,您可以通过从根对象开始检查自己可以对哪些对象进行 GC,在此示例中,Testing 类的对象将是您的根对象,因此您可以在每一步尝试查看是否可以通过从根对象开始到达特定对象,任何从根树无法到达的对象都符合 GC 条件。

public class Testing {

    public static void main(String[] args) {
        test1();
        test2();
    }

    private static void test1() {
        Testing t = new Testing();
        D d = t.new D();
        d.m1();
        System.gc();
        d.m2();
        System.gc();
        // do some more things...
    }

    private static void test2() {
        System.gc();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }
}

public class A3 {
    B b = new B();

    public B getB() {
        return b;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("A3 is garbage collected");
    }
}

public class B {
    public void m1() {
        System.out.println("B.m1");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("B is garbage collected");
    }
}

public class D {
    B bd2;

    public void m1() {
        A3 a3 = new A3();
        bd2 = a3.getB();
    }

    public void m2() {
        bd2.m1();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("D is garbage collected");
    }
}

【讨论】:

  • 您混淆了垃圾收集和终结。即使System.gc() 立即收集对象(不能保证),在垃圾收集确定对象有资格完成完成之后 后,完成会发生任意长时间。它甚至可能永远不会发生。这就是你有时需要调用sleep 来查看finalizer 的 效果的原因,gc 本身的及时性是不可观察的。除此之外,程序的行为正如您所期望的那样,因为它主要是解释运行的。优化的执行可以表现出完全不同的行为。
  • 要查看相互矛盾的行为,请尝试this answer 的程序或找到here 的扩展变体。
  • @Holger 好的,谢谢,我一定会检查的。
【解决方案2】:

我了解到,一旦 A3 的对象被 GC(或符合 GC 条件),B 的对象也将被 GC(或符合 GC 条件)。

这不一定是真的,只要有办法引用对象B,它就没有资格进行GC。

现在,让我们考虑下面的情况,根据我在 d.m1(); 之后的理解,堆上的 a3 对象将符合 GC 条件,但我认为 b 对象不符合 GC 条件。

这基本上是正确的。由于可以通过对象 d 引用对象 b,因此 b 不符合垃圾回收条件。

有资格获得 GC 并不意味着它会被 GC'ed。

【讨论】:

  • 但是a3 不会在d.m1() 完成后立即被GC'ed 吗?
  • 不是真的“尽快”,但它会在下一个 GC 周期中发生。
  • 我按问题更新,请看结尾。我不想争论“有资格获得 GC”和“实际获得 GC”。
  • 我想我理解你的困惑。对象a3 持有对对象b 的引用。它不“拥有”该对象。所以 GC'ing a3 不会自动 GC 对象 b。对象 b 只会在没有更多引用时才会被 GC。
  • @pjj 你可以实现概念所有权,就像String 通过从不向任何人分发引用来拥有它的数组。但是对于垃圾收集器来说,概念是无关紧要的,只有现有的引用才是重要的。
【解决方案3】:

这里对情况2的理解是正确的。一旦 d.m1() 完成,a3 就有资格进行 GC(但不一定会获得 GC,因为 GC 取决于 JVM),但由于 B 在 m1() 之外的对象 bd2 中保存引用,它将没有资格获得 GC。

您可以通过这篇关于 JVM 内存管理的精彩文章来深入了解:

JVM memory Management

【讨论】:

  • +1。很好的尝试,下次尝试一步一步的解释,这样你的答案会吸引更多的注意力。
【解决方案4】:

将此转移到答案,因为评论太长了。

会发生什么?

首先,@Shadab 是对的。对象a3 将被收集,而对象b 仍然存在。

直观解释

您的困惑似乎源于对问题的面向对象的看法。

基本上,您将A 视为一辆汽车,将b 视为它的一个轮子。因此,当看到这样的问题时,合乎逻辑的问题是 - 当我压碎汽车然后它就消失了,作为汽车一部分的车轮怎么可能还存在?乍一看,即使其他人被“访问”了方向盘。

这不是它的工作方式w.r.t内存管理,这也是隐喻会破坏的地方:

  • 当您创建汽车 A 时,它也会创建一个轮子 B
  • 这两种东西都独立存在于内存中。
  • 面向对象的比喻在这里中断,因为构成汽车的对象以及在汽车制造时构建的对象实际上并不是汽车的“部分”。只有对这些对象的引用才是汽车的一部分,但数据本身不是。

比喻过度延伸

如果你想对真正发生的事情有某种心理印象,你可以试着这样看:

  • A 成为一辆车。
  • B成为轮子
  • C 成为螺栓
  • 一个轮子B“由”五个螺栓C“组成”,五个对象C是对象B的成员
  • 一辆汽车A“由”四个轮子B“组成”,四个对象B是对象A的成员。

当你创建A

A a = new A();

您还创建了四个B 实例,这些实例又创建了二十个C 实例。但是,对于内存中的对象,您的车轮不是用螺栓安装在汽车上的。汽车A 躺在停车场(这是你的记忆),里面有四张关于它的轮子的明信片(参考)。四个轮子也躺在停车场的其他地方,每个轮子上都贴着五张各自螺栓的明信片。二十个螺栓也躺在停车场的某个地方。

也许此时您开始明白,为什么这种面向对象的方法不一定适合理解这种内存管理的工作方式以及隐喻是如何被拉得很细的。

如果你毁坏停车场里的汽车,现在会发生什么?车A没了,四个轮子B的明信片也没了。但这并不意味着轮子也会被破坏。仅当在整个停车场中 GC 找不到任何具有特定车轮实例的明信片时,它也被允许销毁该车轮。但是,如果有另一个对象持有这些明信片,则轮子不会被垃圾收集。螺栓也是如此。

底线

对象不包含对象。对象持有对对象的引用。只有在没有进一步引用对象时才会对对象进行垃圾收集。在数据方面,这些东西是相互独立的,即使 OOP 模型在天真地看待它时可能会提出其他建议。

【讨论】:

    猜你喜欢
    • 2017-05-31
    • 2011-06-20
    • 1970-01-01
    • 2020-07-28
    • 1970-01-01
    • 2021-06-14
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多