本文共 4407 字,大约阅读时间需要 14 分钟。
敬告:本篇文章是我原创所写,首发于 ,未经本人授权任何网站、公众号、App 不允许转载,授权的网站、公众号、App 需明确标识本篇文章首发地址。需转载请联系 494324190@qq.com
在 .NET 中垃圾回收和资源清理是重中之重的内容,也是所有程序都必须用到的机制,但是有很大一部分开发人员并不知道垃圾回收和资源清理的原理。那么,我将通过这篇文章向各位读者详细讲解一下垃圾回收和资源清理。
.NET中垃圾回收是运行时的核心功能,它的作用是回收不再被引用的对象所占用的内存。这里我们要注意垃圾回收器只回收内存资源而不处理其他资源。此外垃圾回收器是根据是否存在任何引用来决定要清理那些东西,也就是说垃圾回收器处理的是不被引用的引用对象,并且只能回收堆上的内存。
WeakReference Data;public FileStream Date(){ FileStream fs= (FileStream)Data.Target; if(data!=null) { return data; } // more code Data.Target=data; return data;}上面的代码是一个标准的创建弱引用的代码,我们可以看到在代码中对变量 data 进行了 null 判断,我们可以通过这个判断来检查垃圾回收器是否将其回收。这里还有一个关键代码 FileStream fs= (FileStream)Data.Target; 这里将弱引用赋值给了强引用,这样可以避免在检查 null 后和访问数据前,发生垃圾回收器回收弱引用。
在前面一小节开头我们说过垃圾回收之回收内存中的对象,那么如果我们需要回收其他资源呢,例如数据库连接、句柄、外部设备。这时我们就需要用到资源清理。
终结器
终结器是一个允许开发人员通过代码来清理类资源的东西。终结器最大的特征是它不能在代码中显式调用,只有垃圾回收器负责对对象的实例调用终结器,因此开发人员无法在编译时确定终结器在何时执行,只能够确定终结器时对象中最后一次被调用的地方。 终结器的定义也很简单,只需要在类名之前加一个 ~ 符号即可。class Demo{ public Demo(string name) { //more code } ~Demo() { Close(); } public void Close() { //more code } //more code}
上述代码我们就定义了一个简单的终结器,我们定义终结器的时候需要注意以下四点:
因为终结器是在自己的线程中执行的,因此如果终结器中存在一个未处理的异常就会很难诊断发现,因为造成异常的情况并不清晰透明。所以我们必须避免在终结器中引发异常。
using
虽然终结器可以帮助我们在忘记显式调用必要清理代码的时候执行清理,但是因为终结器的运行存在不确定性,因此我们只能将它作为备用机制。正常情况下我们可以使用 using 。 C# 中的 IDisposable 接口的 Dispose 方法为我们提供了实现细节。我们先来看一段代码。class Demo{ MyFileStream fs =new myFileStram(); //more code fs.Dispose(); //more code}class MyFileStream:IDisposable{ public MyFileStream(string path) { //more code } //more code ~MyFileStream { Dispose(false); } public void Close() { Dispose(); } public void Dispose() { Dispose(true); System.GC.SuppressFinalize(); } public void Dispose(bool para) { // more code }}
上述代码中我们显式调用了 MyFileStream 类的 Dispose 方法。 Dispose 方法主要用来清理已经用过的资源,但是这里存在一个问题,当我们调用 Dispose 方法时有可能会发生异常,这时我们就无法正确调用 Dispose 方法了,为了避免这个问题我们需要加入 try…finally 块。但是我们无法保证开发人员每次都会写 try…finally ,这时我们可以使用 C# 提供的 using 语句,我们将上面的调用代码修改一下:
class Demo{ using(MyFileStream fs =new myFileStram()) { //more code }}
这段代码最终生成的 CIL 代码和使用 try…finally 块生成的代码完全一样。
垃圾回收、终结和 IDisposable
在上一小节的代码中我们看到在 Dispose 方法中我们调用了 System.GC.SuppressFinalize(); ,它的作用是从终结队列中移除 MyFileStream 实例。因为所有清理都在Dispose 方法中完成了,而不是等着终结器执行。如果不调用 System.GC.SuppressFinalize() 方法实例将会一直在终结队列中,只有当终结方法被调用之后才能在垃圾回收器中被回收,那么这就造成了托管资源垃圾回收处理时间的延迟。 Dispose 方法中调用了 Dispose(bool para) 方法,在这个方法里我们可以清理资源并阻止终结器。其次,我们定义了 Close 方法来调用 Dispose(bool para) 方法,这样终结器就可以调用 Dispose(bool para) 方法来关闭释放资源。针对前一小结的代码需要有如下几点注意:在某些特殊情况下垃圾回收的对象有可能会被无意中重新引用一个待终结的对象。这样,被重新引用的对象就不再是不可访问的,所以不能当作垃圾被回收掉。假如对象的终结方法已经运行,那么除非显式标记为要进行终结,否则终结方法不一定会再次运行。
这篇文章详细讲解了垃圾回收和资源清理相关的知识,对于部分开发人员来说这部分知识可能晦涩难懂,但是只要在实际项目中上手使用,我相信就可以很快的掌握和理解。