垃圾收集器命令
Garbarge collector order
我知道 .Net 垃圾收集器在销毁对象时不能保证任何顺序,但我对以下行为感到非常惊讶:
using System;
using System.Collections.Generic;
namespace ConsoleApplication2 {
class Program {
class A {
public A() { i = 1; }
private int i;
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~A() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Console.WriteLine("Disposed A");
}
}
class B {
static int p = 0;
public B(A a) {
this.a = a;
j = p++;
}
~B() {
Console.WriteLine("Disposed " + j.ToString());
}
private int j;
private A a;
}
static void Main(string[] args) {
A a_obj = new A();
List<B> bs = new List<B>();
for (int i = 0; i < 10; i++) {
B b = new B(a_obj);
bs.Add(b);
}
}
}
}
这次执行有以下(可能的)输出:
处置9
已处置 2
已处理 1
已处置 0
处置A
处置8
处置 7
处置 6
处置 5
处置 4
处置 3
我能理解 B 对象的顺序不是确定的,而是
如果仍然有一些 B 对象引用了 a_obj 并且还没有被销毁,那么垃圾收集器怎么可能销毁 a_obj?
如果引用 a_obj
的元素中的 none 个被引用,GC 可以收集它,因为应用程序根无法再次获得对它的引用。
这是a good tutorial on C# object lifecycle
的相关部分
在垃圾回收过程中,运行时将调查托管堆上的对象以确定应用程序是否仍可访问(根)它们。为此,CLR 将构建一个对象图,它代表堆上的每个可达对象。对象图用于记录所有可达的对象。
[...]
构建图后,无法访问的对象将被标记为垃圾。
输出是在进程关闭时产生的,此时GC正在清理进程中分配的整个内存,对象之间的引用不影响清理顺序。
.NET 垃圾收集器不使用引用计数,因此对 a_obj
的引用有多少并不重要。重要的是从根可以访问哪些对象(如静态变量)。在您的情况下,所有对象(A
和所有 B
s)同时变得无法从 root 访问,因此都符合垃圾收集的条件,它们的相互引用无关紧要。如果 A
和 B
都不可访问并且将在同一个循环中被收集,那么为什么还要考虑发生这种情况的顺序呢?此外,如果顺序取决于哪些对象引用了哪些 - 考虑一下它将如何收集相互引用的对象?
这里也引用了 documentation 中的一句话,完全符合您的情况:
The finalizers of two objects are not guaranteed to run in any
specific order, even if one object refers to the other. That is, if
Object A has a reference to Object B and both have finalizers, Object
B might have already been finalized when the finalizer of Object A
starts.
另请注意,首先所有的终结器都会 运行,然后对象才会真正死亡,并且它们占用的内存可能会被重用(您甚至可以通过 GC.ReRegisterForFinalize
ressurect 终结器中的对象)。因此您可以从 B
的终结器访问 A
,即使此时 A
的终结器可能已经是 运行。显然你应该避免这样做。
我知道 .Net 垃圾收集器在销毁对象时不能保证任何顺序,但我对以下行为感到非常惊讶:
using System;
using System.Collections.Generic;
namespace ConsoleApplication2 {
class Program {
class A {
public A() { i = 1; }
private int i;
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~A() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Console.WriteLine("Disposed A");
}
}
class B {
static int p = 0;
public B(A a) {
this.a = a;
j = p++;
}
~B() {
Console.WriteLine("Disposed " + j.ToString());
}
private int j;
private A a;
}
static void Main(string[] args) {
A a_obj = new A();
List<B> bs = new List<B>();
for (int i = 0; i < 10; i++) {
B b = new B(a_obj);
bs.Add(b);
}
}
}
}
这次执行有以下(可能的)输出:
处置9
已处置 2
已处理 1
已处置 0
处置A
处置8
处置 7
处置 6
处置 5
处置 4
处置 3
我能理解 B 对象的顺序不是确定的,而是 如果仍然有一些 B 对象引用了 a_obj 并且还没有被销毁,那么垃圾收集器怎么可能销毁 a_obj?
如果引用 a_obj
的元素中的 none 个被引用,GC 可以收集它,因为应用程序根无法再次获得对它的引用。
这是a good tutorial on C# object lifecycle
的相关部分在垃圾回收过程中,运行时将调查托管堆上的对象以确定应用程序是否仍可访问(根)它们。为此,CLR 将构建一个对象图,它代表堆上的每个可达对象。对象图用于记录所有可达的对象。 [...] 构建图后,无法访问的对象将被标记为垃圾。
输出是在进程关闭时产生的,此时GC正在清理进程中分配的整个内存,对象之间的引用不影响清理顺序。
.NET 垃圾收集器不使用引用计数,因此对 a_obj
的引用有多少并不重要。重要的是从根可以访问哪些对象(如静态变量)。在您的情况下,所有对象(A
和所有 B
s)同时变得无法从 root 访问,因此都符合垃圾收集的条件,它们的相互引用无关紧要。如果 A
和 B
都不可访问并且将在同一个循环中被收集,那么为什么还要考虑发生这种情况的顺序呢?此外,如果顺序取决于哪些对象引用了哪些 - 考虑一下它将如何收集相互引用的对象?
这里也引用了 documentation 中的一句话,完全符合您的情况:
The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already been finalized when the finalizer of Object A starts.
另请注意,首先所有的终结器都会 运行,然后对象才会真正死亡,并且它们占用的内存可能会被重用(您甚至可以通过 GC.ReRegisterForFinalize
ressurect 终结器中的对象)。因此您可以从 B
的终结器访问 A
,即使此时 A
的终结器可能已经是 运行。显然你应该避免这样做。