.NET中GC如何判断一个对象仍然在被使用?

(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()这行代码时运行垃圾回收时的情况。

.NET中GC如何判断一个对象仍然在被使用?

从上图中可以看出,在执行到代码Console.ReadKey()时,存活的根引用有staticEmployee和a,前者因为它是一个公共静态变量,而后者则因为后续代码还会使用到a。

通过这两个存活的根引用,GC会找到一个非跟引用staticEmployee.boss,并且发现三个仍然存活的对象。而b的对象则将被视为不再使用从而被释放。

画外音:更简单地确保b对象不再被视为在被使用的方法时把b的引用置为null,即b=null;

此外,当一个从根引用触发的遍历抵达一个已经被视为在使用的对象时,将结束这一个分支的遍历,这样做可以避免陷入死循环。