(1).NET GC分代的基本算法
在.NET中引用类型对象实例通常通过引用来访问,而GC判断堆中的对象是否仍然在被使用的依据也是引用。简单地说:当没有任何引用指向堆中的某个对象实例时,这个对象就被视为不再使用。.
在GC执行垃圾回收时,会把引用分为以下两类:
(1)根引用:往往指那些静态字段的引用,或者存活的局部变量的引用;
(2)非根引用:指那些不属于根引用的引用,往往是对象实例中的字段。
垃圾回收时,GC从所有仍在被使用的根引用出发遍历所有的对象实例,那些不能被遍历到的对象将被视为不再被使用而进行回收。我们可以通过下面的一段代码来直观地理解根引用和非根引用:
class Program
{
public static Employee staticEmployee;
static void Main(string[] args)
{
staticEmployee = new Employee(); // 静态变量
Employee a = new Employee(); // 局部变量
Employee b = new Employee(); // 局部变量
staticEmployee.boss = new Employee(); // 实例成员
Console.ReadKey();
Console.WriteLine(a);
}
}
public class Employee
{
public Employee boss;
public override string ToString()
{
if(boss == null)
{
return "No boss";
}
return "One boss";
}
}
上述代码中一共有两个局部变量和一个静态变量,这些引用都是根引用。而其中一个局部变量 a 拥有一个成员实例对象,这个引用就是一个非跟引用。
下图展示了代码执行到Console.ReadKey()这行代码时运行垃圾回收时的情况。
从上图中可以看出,在执行到代码Console.ReadKey()时,存活的根引用有staticEmployee和a,前者因为它是一个公共静态变量,而后者则因为后续代码还会使用到a。
通过这两个存活的根引用,GC会找到一个非跟引用staticEmployee.boss,并且发现三个仍然存活的对象。而b的对象则将被视为不再使用从而被释放。
画外音:更简单地确保b对象不再被视为在被使用的方法时把b的引用置为null,即b=null;
此外,当一个从根引用触发的遍历抵达一个已经被视为在使用的对象时,将结束这一个分支的遍历,这样做可以避免陷入死循环。