110,533
社区成员
发帖
与我相关
我的任务
分享
C# 使用自动内存管理,它使开发人员不再需要以手动方式分配和释放由对象占用的内存。自动内存管理策略由垃圾回收器实现。一个对象的内存管理生存周期如下所示:
当创建对象时,将为其分配内存,运行构造函数,该对象被视为活对象。
如果该对象或它的任何部分在后续执行过程中不再可能被访问了(除了运行它的析构函数),则该对象被视为不再被使用,可以销毁。C# 编译器和垃圾回收器可以通过分析代码,确定哪些对象引用可能在将来被使用。例如,如果范围内的某个局部变量是现有的关于此对象的唯一的引用,但在当前执行点之后的任何后续执行过程中,该局部变量都不再可能被引用,那么垃圾回收器可以(但不是必须)认为该对象不再被使用。
一旦对象符合销毁条件,在稍后某个时间将运行该对象的析构函数(第 10.12 节)(如果有的话)。除非被显式调用所重写,否则对象的析构函数只运行一次。
一旦运行对象的析构函数,如果该对象或它的任何部分无法由任何可能的执行继续(包括运行析构函数)访问,则该对象被视为不可访问,可以回收。
最后,在对象变得符合回收条件后,垃圾回收器将释放与该对象关联的内存。
垃圾回收器维护对象的使用信息,并利用此信息做出内存管理决定,如在内存中的何处安排一个新创建的对象、何时重定位对象以及对象何时不再被使用或不可访问。
与其他假定存在垃圾回收器的语言一样,C# 也被设计成使垃圾回收器可以实现广泛的内存管理策略。例如,C# 并不要求一定要运行析构函数,不要求对象一符合条件就被回收,也不要求析构函数以任何特定的顺序或在任何特定的线程上运行。
垃圾回收器的行为在某种程度上可通过类 System.GC 的静态方法来控制。该类可用于请求执行一次回收操作、运行(或不运行)析构函数,等等。
由于垃圾回收器在决定何时回收对象和运行析构函数方面可以有很大的选择范围,它的一个符合条件的实现所产生的输出可能与下面的代码所显示的不同。程序
using System;
class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}
}
class B
{
object Ref;
public B(object o) {
Ref = o;
}
~B() {
Console.WriteLine("Destruct instance of B");
}
}
class Test
{
static void Main() {
B b = new B(new A());
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
创建类 A 的一个实例和类 B 的一个实例。当给变量 b 赋值 null 后,这些对象变得符合垃圾回收条件,这是因为从此往后,任何用户编写的代码不可能再访问这些对象。输出可以为
Destruct instance of A
Destruct instance of B
或
Destruct instance of B
Destruct instance of A
这是因为该语言对于对象的垃圾回收顺序没有强加约束。
在微妙的情况中,“符合销毁条件”和“符合回收条件”之间的区别非常重要。例如,
using System;
class A
{
~A() {
Console.WriteLine("Destruct instance of A");
}
public void F() {
Console.WriteLine("A.F");
Test.RefA = this;
}
}
class B
{
public A Ref;
~B() {
Console.WriteLine("Destruct instance of B");
Ref.F();
}
}
class Test
{
public static A RefA;
public static B RefB;
static void Main() {
RefB = new B();
RefA = new A();
RefB.Ref = RefA;
RefB = null;
RefA = null;
// A and B now eligible for destruction
GC.Collect();
GC.WaitForPendingFinalizers();
// B now eligible for collection, but A is not
if (RefA != null)
Console.WriteLine("RefA is not null");
}
}
在上面的程序中,如果垃圾回收器选择在 B 的析构函数之前运行 A 的析构函数,则该程序的输出可能是:
Destruct instance of A
Destruct instance of B
A.F
RefA is not null
请注意,虽然 A 的实例被当作“没有被使用”,并且 A 的析构函数已被运行过了,但仍可能从其他析构函数调用 A 的方法(此例中是指 F)。还请注意,运行析构函数可能导致对象再次从主干程序中变得可用。在此例中,运行 B 的析构函数导致了先前没有被使用的 A 的实例变得可从当前有效的引用 Test.RefA 访问。调用 WaitForPendingFinalizers 后,B 的实例符合回收条件,但由于引用 Test.RefA 的缘故,A 的实例不符合回收条件。
为了避免混淆和意外的行为,好的做法通常是让析构函数只对存储在它们对象本身字段中的数据执行清理,而不对它所引用的其他对象或静态字段执行任何操作。