.net作为托管乎台,释放内存和指针的任务已经交由虚拟机(CLR)来管理了,即所谓的GC。.net对于什么样的对象已经是垃圾说得很清楚,即“对象已经不可达”,这一点还算比较好理解,运行时所有的对象组织成为一张图,而在图论中,“可达性”是作为一个非常重要的性质来进行讨论的。虽然GC的机制却有很多东西并没有告诉我们,但是的确那些东西不需要我们太过于关心,比如:GC何时触发,某次GC回收了具体哪个对象等等。
我们知道的是,有一类对象比较特殊,就是实现了终结器的类型所构造的对象,在默认情况下(除非调用了GC.SuppressFinalize(Object obj)方法,这在标准IDispose模式中会见到),GC会在回收该对象的时候,调用对象的终结器。这样就引发了一个问题:如果在对象的终结器中,又使对象变得“可达”了,那么这个对象还可用吗?
这个问题纯粹是个学术性的问题,我不认为哪个应用会有这种愚蠢的需求:已经认定一个东西没用,从而把它扔到了垃圾堆,但是等到清理垃圾的人想要把它运走埋掉的时候,我还从人家手里把它抢回来。不过话说回来,这个学术性的问题还比较有意思,至少它能告诉我们一个对象的终结器是不是能被调多次,还有在调用对象的终结器之前,GC是否已经破坏了该对象。
所谓无代码无真相,我用最简单的方式,实现了一个“在终结器里把对象变得可达”,代码如下:
在MyClass1这个类型当中,有一个static的Cache,以便在调用终结器的时候将对象的this指针缓存,从而使对象再次可达。而ItemCache类的Clear方法调用GC.ReRegisterForFinalize(Object obj)方法重新注册对象的终结器(由于该对象的终结器已经被调用过一回了,所以当对象在被“救活”之后再次变成不可达的时候,GC将不再调用对象的终结器),这样的话,每当对象变得不可达的时候,GC会调用它的终结器,而这个方法又使对象变得可达,从而把这个对象“救活”,使之生生不息。
那么,在GC决定回收对象并且调用对象终结器,对象是否被破坏了呢?我做的简单的例子是为对象添加一个全局的私有成员,在构造器内为该私有对象实例化,经过测试发现,即使已经调用了对象的终结器,只要在终结器内没有破坏对象,GC也不会去破坏它(即私有的成员仍然非null)。即在终结器内被救活的对象跟它没死之前没有任何区别,还拥有它生前一切的功能。这是一个大胆的假设,我没有严格去证明它。
需要提一点的是,GC肯定不会在程序的主线程或者任何我们开启的线程中调用对象的终结器,因此做这个测试需要用到线程的wait和notify(GC.WaitForFullGCComplete()方法也许就可以block当前线程直到GC回收结束,但是这个.net Framework 3.5sp1加入的方法我没弄明白到底有什么用和怎么用),我是用AutoResetEvent实现的,直到GC调用终结器并且在方法内又把对象救活之后,我们再继续主线程的其他任务,以免出现NullReferenceException。
这个程序说明.net的GC在调用终结器的时候,仍然没有把在托管堆上分配给对象的空间回收(即释放内存),于是它给了我们一个把处于濒死状态的对象救活的机会,虽然这样并不怎么合理。这个MyClass1类型完全是在托管环境上实现的,没有申请任何的非托管资源,我也没为其实现IDispose接口。如文中提到的,这只是一个学术性的研究,没有任何实用价值。
如果有什么地方论证得不够严谨,还望高手不吝赐教。
缥缈落花街 月圆月缺 望峦山平川 雁返君未还 怆然晚春残 忆天上人间