组合情况下的对象生命周期和 GC
Object lifecycle and GC in case of Composition
下面是一个组合的例子:
public class A3 {
B b = new B();
}
我了解到,一旦 A3
的对象被 GC 处理(或符合 GC 条件),B
的对象也将被 GC 处理(或符合 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”之间的区别,所以为了方便起见,假设对象将立即被 GC'一旦符合条件就编辑。
What I learned is that as soon as object of A3 is GC'ed (or becomes eligible for GC), object of B will also be GC'ed (or becomes eligible for GC).
这不一定,只要有办法引用对象B,它就没有资格被GC。
Now, let's consider below situation, so as per my understanding after d.m1();, a3 object on heap will become eligible for GC, but I don't think that b object becomes eligible for GC.
这大部分是正确的。由于可以通过对象 d 引用对象 b,因此 b 不符合垃圾回收条件。
符合 GC 条件并不意味着它将被 GC。
这里对情况2的理解是正确的。一旦 d.m1() 完成,a3 就有资格进行 GC(但不一定会进行 GC,因为 GC 取决于 JVM)但是由于 B 在 m1() 之外的对象 bd2 中持有引用,因此它将没有资格获得 GC。
您可以阅读这篇关于 JVM 内存管理的精彩文章来深入了解:
由于评论太长,正在将其转移到答案中。
发生了什么?
首先,@Shadab 是对的。对象 a3
将被收集,而对象 b
仍然存在。
直观的解释
您的困惑似乎源于对问题的面向对象观点。
基本上,您将 A
视为一辆汽车,将 b
视为其轮子之一。所以当这样看问题时,合乎逻辑的问题是 - 当我压碎汽车然后它消失了,作为汽车一部分的车轮怎么还存在? 那不乍一看很有意义,即使其他人被授予“访问”方向盘的权限。
这不是它的工作方式 w.r.t 内存管理,这也是隐喻将打破的地方:
- 当您创建汽车时
A
它也会创建一个轮子 B
。
- 这两个东西都独立存在于内存中。
- 面向对象的隐喻在这里失效了,因为构成汽车的对象以及在制造汽车时构建的对象实际上并不是汽车的“一部分”。只有对这些对象的引用才是汽车的一部分,但数据本身不是。
比喻言过其实
如果您想对实际发生的事情有某种心理印象,您可以尝试这样看:
- 让
A
成为一辆汽车。
- 设
B
为轮子
- 设
C
为螺栓
- 一个轮子
B
“由”五个螺栓“组成”C
,即五个对象C
是一个对象[=16的成员=]
- 一辆汽车
A
由四个轮子“组成”B
,即四个对象B
是一个对象的成员A
.
创建时A
A a = new A();
您还创建了 B
的四个实例,这些实例又创建了 C
的二十个实例。但是,对于内存中的对象,您的车轮并没有用螺栓安装在汽车上。汽车 A
躺在停车场(这是你的记忆),里面有四张车轮明信片(参考资料)。四个轮子也躺在停车场的其他地方,每个轮子上面都有五张明信片,上面写着各自的螺栓。二十个螺栓也躺在停车场的某个地方。
也许在这一点上你开始明白,为什么这种面向对象的方法不一定适合理解这种内存管理的工作方式以及这个比喻是如何被拉得很长的。
那么如果你在停车场毁了车会怎样?车A
没了,四轮的明信片B
也没有了。但这并不意味着车轮也被破坏了。仅当 GC 在整个停车场中找不到任何具有特定车轮实例的明信片时,它才被允许销毁车轮。但是如果有另一个对象持有这些明信片,那么轮子就不会被垃圾回收。螺栓也是如此。
底线
对象不持有对象。对象持有对对象的引用。如果没有对它们的进一步引用,对象只会被垃圾收集。数据方面这些东西是相互独立的,即使 OOP 模型在天真地看待它时可能会提出不同的建议。
@Shadab 和@Koenigsberg 所说的是正确的,这里有一些解释和演示 - 基本上我所做的是我已经覆盖了 finalize()
方法,以便向您展示对象如何获得 GC'编辑
- 这里要注意的关键是,只要方法的堆栈帧从堆栈中取出,任何在方法内部创建且未从该方法传递到其他任何地方的对象都将符合 GC 条件。这就是为什么您会在
d.m1();
结束后立即看到“A3 已被垃圾回收”。
- 出于同样的原因,您会在
test1();
结束后看到“D 已被垃圾回收”。如果您注释掉 test2();
那么“D is garbage collected”将不会被打印出来,因为一旦 test1
方法的栈帧被移除,就没有(强制的)GC,并且只要 test1
方法的栈帧=15=] 存在于堆栈中,无论如何无法删除对象 d
。
- 对象 'b' 的 GC 有点棘手,如果你不让线程休眠几毫秒,那么它就不会被 GC,遗憾的是我不知道确切的解释这个。
另一个需要注意的重要点是,您可以从根对象开始检查自己可以对哪些对象进行 GC,在此示例中 class 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");
}
}
下面是一个组合的例子:
public class A3 {
B b = new B();
}
我了解到,一旦 A3
的对象被 GC 处理(或符合 GC 条件),B
的对象也将被 GC 处理(或符合 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”之间的区别,所以为了方便起见,假设对象将立即被 GC'一旦符合条件就编辑。
What I learned is that as soon as object of A3 is GC'ed (or becomes eligible for GC), object of B will also be GC'ed (or becomes eligible for GC).
这不一定,只要有办法引用对象B,它就没有资格被GC。
Now, let's consider below situation, so as per my understanding after d.m1();, a3 object on heap will become eligible for GC, but I don't think that b object becomes eligible for GC.
这大部分是正确的。由于可以通过对象 d 引用对象 b,因此 b 不符合垃圾回收条件。
符合 GC 条件并不意味着它将被 GC。
这里对情况2的理解是正确的。一旦 d.m1() 完成,a3 就有资格进行 GC(但不一定会进行 GC,因为 GC 取决于 JVM)但是由于 B 在 m1() 之外的对象 bd2 中持有引用,因此它将没有资格获得 GC。
您可以阅读这篇关于 JVM 内存管理的精彩文章来深入了解:
由于评论太长,正在将其转移到答案中。
发生了什么?
首先,@Shadab 是对的。对象 a3
将被收集,而对象 b
仍然存在。
直观的解释
您的困惑似乎源于对问题的面向对象观点。
基本上,您将 A
视为一辆汽车,将 b
视为其轮子之一。所以当这样看问题时,合乎逻辑的问题是 - 当我压碎汽车然后它消失了,作为汽车一部分的车轮怎么还存在? 那不乍一看很有意义,即使其他人被授予“访问”方向盘的权限。
这不是它的工作方式 w.r.t 内存管理,这也是隐喻将打破的地方:
- 当您创建汽车时
A
它也会创建一个轮子B
。 - 这两个东西都独立存在于内存中。
- 面向对象的隐喻在这里失效了,因为构成汽车的对象以及在制造汽车时构建的对象实际上并不是汽车的“一部分”。只有对这些对象的引用才是汽车的一部分,但数据本身不是。
比喻言过其实
如果您想对实际发生的事情有某种心理印象,您可以尝试这样看:
- 让
A
成为一辆汽车。 - 设
B
为轮子 - 设
C
为螺栓 - 一个轮子
B
“由”五个螺栓“组成”C
,即五个对象C
是一个对象[=16的成员=] - 一辆汽车
A
由四个轮子“组成”B
,即四个对象B
是一个对象的成员A
.
创建时A
A a = new A();
您还创建了 B
的四个实例,这些实例又创建了 C
的二十个实例。但是,对于内存中的对象,您的车轮并没有用螺栓安装在汽车上。汽车 A
躺在停车场(这是你的记忆),里面有四张车轮明信片(参考资料)。四个轮子也躺在停车场的其他地方,每个轮子上面都有五张明信片,上面写着各自的螺栓。二十个螺栓也躺在停车场的某个地方。
也许在这一点上你开始明白,为什么这种面向对象的方法不一定适合理解这种内存管理的工作方式以及这个比喻是如何被拉得很长的。
那么如果你在停车场毁了车会怎样?车A
没了,四轮的明信片B
也没有了。但这并不意味着车轮也被破坏了。仅当 GC 在整个停车场中找不到任何具有特定车轮实例的明信片时,它才被允许销毁车轮。但是如果有另一个对象持有这些明信片,那么轮子就不会被垃圾回收。螺栓也是如此。
底线
对象不持有对象。对象持有对对象的引用。如果没有对它们的进一步引用,对象只会被垃圾收集。数据方面这些东西是相互独立的,即使 OOP 模型在天真地看待它时可能会提出不同的建议。
@Shadab 和@Koenigsberg 所说的是正确的,这里有一些解释和演示 - 基本上我所做的是我已经覆盖了 finalize()
方法,以便向您展示对象如何获得 GC'编辑
- 这里要注意的关键是,只要方法的堆栈帧从堆栈中取出,任何在方法内部创建且未从该方法传递到其他任何地方的对象都将符合 GC 条件。这就是为什么您会在
d.m1();
结束后立即看到“A3 已被垃圾回收”。 - 出于同样的原因,您会在
test1();
结束后看到“D 已被垃圾回收”。如果您注释掉test2();
那么“D is garbage collected”将不会被打印出来,因为一旦test1
方法的栈帧被移除,就没有(强制的)GC,并且只要test1
方法的栈帧=15=] 存在于堆栈中,无论如何无法删除对象d
。 - 对象 'b' 的 GC 有点棘手,如果你不让线程休眠几毫秒,那么它就不会被 GC,遗憾的是我不知道确切的解释这个。
另一个需要注意的重要点是,您可以从根对象开始检查自己可以对哪些对象进行 GC,在此示例中 class 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");
}
}