Angel Lunamaria

Lunamaria's Life...

关于csharp的内存管理与垃圾回收

csharp有GC,所以一个对象一旦没有vallid reference,它所占用的内存无论如何都会被自动回收,但是有些对象中有unmanaged resource,例如file、socket、thread,他们不能简单的回收内存了事,必须由用户代码去释放这些资源。

如何达成这一点,csharp提供了implicit和explicit两种回收模式:implicit回收即依赖GC,GC在回收内存前会检查对象是否有Finalizer,即~Type()方法,如果有则先调用这个方法,再回收内存,这样用户可以在Finalizer里实现对unmanged resource的回收。

implicit回收依靠GC,其行为是非确定的,即回收的顺序、时机、是否会回收都是不确定的。例如A references B,当AB都成为垃圾时,其回收次序是不确定的,如果两者都有Finalizer,其调用此次序也是不确定的,这样会给Finalizer的实现带来困难;另外,不确定的回收时机会导致关键资源不能及时释放,所以m$又提供了一种explicit的回收机制。

explicit的回收机制完全是一个接口,IDisposable,提供一个方法Dispose()。它的行为与普通方法一致,依靠用户显示调用。而且这个接口与GC完全没有关系,调用Dispose()不会触发GC,不会导致GC回收内存,也不会通知GC:“这个对象已经被释放了,你一会只管回收内存,不用调他的Finalizer了”。假设这个Dispose只是输出了一句hello world,你甚至可以在资源的生命周期中间就去call他的Dispose(),然后继续该干嘛干嘛。

总之,这个接口有很多问题,例如它让接口依赖实现;另外,两种回收机制的剥离也导致了很多问题。

1、任何一个对象,只要他持有了一个Disposable资源,则它必须在合适的时候调用Dispose()去显示的回收此资源;如果这个对象本身也是一个资源,则它也必须实现IDisposable,以便在自己被显示回收时把自己持有的资源也显示回收;

2、一个实现IDisposable的资源必须要考虑到:假设用户忘记调用Dispose()了怎么办?所以我们要在Finalizer里调用Dispose(),这样即使用户忘记调用Dispose(),当implicit回收机制被触发时依然可以把这些unmanaged resource回收;

3、用户多次调用Dispose()怎么办。。。

4、当用户忘记dispose,而后implicit机制的Finalizer调用Dispose()时,回收逻辑变了:因为GC的不确定性,它持有的其他Disposable资源可能已经被Dispose了,一不小心就null reference了。。。

m$为此提供了Dispose Pattern,可以说是比较完美的解决了这个问题,只不过代码及其复杂,而且不了解GC机制的人完全搞不懂为什么会这么复杂。。。
http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx

这里还有两篇文章介绍了资源回收的问题,并给出了很详细的解释
http://bentsai.wordpress.com/2011/02/21/idisposable-the-contentious-dispose-pattern/
http://www.codeproject.com/KB/dotnet/idisposable.aspx

World Travel 19City Bloxx TODO

Write a comment

New comments have been disabled for this post.