Java也有GC,当然GC也不是Java首创的,可能Sun的出发点就是要把Java搞得open一点,所以很多东西没有做具体规定,比如GC的算法,还有System.GC()的调用按照Java标准,并不会一定引发一次垃圾回收。之后各路Java虚拟机风起云涌,所以当年我作为Java Developer,理直气壮的没有关心GC 内部的具体细节,这不是一个好借口:)
现在在C#上工作,事实上就MS一家的.Net平台(听说还有一个什么Mono),内部原理定义的很明确,容易了解清楚,这也是一家独大的好处:)
有一点要搞清楚,是.NET的GC,不是C#的GC,C#只是产生.NET能够执行代码的语言之一。
C#借鉴了很多C++和Java的东西,对于析构函数,表现得很想C++,也是类名前面加一个~符号,英文叫tlide,但是C#编译器会把这个东西做一些变化,使得表面上很象C++,但骨子里还是按照.NET的规矩做事。
比如我们写一个类的析构函数
~Dummy() { //do something here }
C#编译器会将其当下面的内容编译
protected override void Finalize()//连函数名都改了哦 { try { //do something here } finally { base.Finalize(); } }
用ILDASM.exe可以看到产生的最终IL代码中,根本就没有~Dummy这个函数,因为C#编译器自动把它变成protected override void Finalize()了。嗯,Java里面也有一个finalize()函数,似乎就没有怎么被用过。
要是手工添加一个protected override void Finalize()函数,编译器会给一个编译错误:“Do not override object finalize. Instead, provide a destructor.”
如果一个类只涉及内存资源,没有必要自己定义析构函数,因为GC能够在回收对象的时候把内存也回收了;需要自己定义析构函数往往是因为涉及到外部资源(unmanaged resources),如系统文件、unmanaged code分配的内存,这些东西GC自个搞不定,需要类在Finalize中说明白哪些东西要释放。
GC有一个Finalization Queue,对于定义了Finalize的类的对象,创建的时候会在Queue里面挂号,在真正做GC的时候,就对这个Queue里面引用的对象挨个调用Finalize,然后再回收内存。这样保证涉及外部资源对象的“析构函数”在内存释放之前调用,又避免了对不涉及外部资源的类也要调用“析构函数”,所以,不要没事定义一个空的析构函数,这会导致无谓的Finalization Queue的延长,影响效率。
和Java一样,理论上我们不要操心何时做GC,但是也提供了强制做GC的API,Java中的System.gc()只是给虚拟机(VM)一个suggestion,VM可做可不做,VM觉得没必要做我们也末办法:) C#中可用System命名空间中的GC.Collect()来强制做一次垃圾回收,按照字面的说法,.NET肯定会做一次GC,但是不保证所有的“垃圾”真的会在这次行动中被回收,呵呵,这就和Java差不多了。
由于GC的过程需要中断所有正在运行的线程,所以不要频繁的调用GC.Collect(),不然会很影响性能,一般只有确性程序刚刚释放了一大块内存的时候才调用它。我只在写小段test code的时候使用这个API。
还有一个函数GC.WaitForPendingFinalizers(),和GC.Collect()配合使用可以看到明显效果,因为GC.Collect只是引发GC,并不等待GC结束。当然,严格说来GC.WaitForPendingFinalizers()也不是等到GC完成才返回,它实际上等到Finalization Queue上的Finalizer都被调用过了就返回,这时候内存还没有回收呢。