c#

位置:IT落伍者 >> c# >> 浏览文章

自动内存管理机制深入剖析-C#分析篇


发布日期:2022年05月21日
 
自动内存管理机制深入剖析-C#分析篇

NET Framework中内存中的资源(即所有二进制信息的集合)分为托管资源非托管资源托管资源必须接受NET Framework的CLR(通用语言运行时)的管理(诸如内存类型安全性检查)而非托管资源则不必接受NET Framework的CLR管理 (了解更多区别请参阅NET Framework或C#的高级编程资料)

托管资源在NET Framework中又分别存放在两种地方: 堆栈托管堆(以下简称);规则是所有的值类型(包括引用和对象实例)和引用类型的引用都存放在堆栈而所有引用所代表的对象实例都保存在堆中

在C#中释放托管资源是可以自动通过垃圾回收器完成的(注意垃圾回收机制是NET Framework的特性而不是C#的)但具体来说仍有些需要注意的地方:

值类型(包括引用和对象实例)和引用类型的引用其实是不需要什么垃圾回收器来释放内存的因为当它们出了作用域后会自动释放所占内存(因为它们都保存在堆栈学过数据结构可知这是一种先进后出的结构);

只有引用类型的引用所指向的对象实例才保存在而堆因为是一个自由存储空间所以它并没有像堆栈那样有生存期(堆栈的元素弹出后就代表生存期结束也就代表释放了内存)并且非常要注意的是垃圾回收器只对这块区域起作用;

垃圾回收器也许并不像许多人想象的一样会立即执行(当堆中的资源需要释放时)而是在引用类型的引用被删除和它在中的对象实例被删除中间有个间隔为什么呢? 因为垃圾回收器的调用是比较消耗系统资源的因此不可能经常被调用!

(当然用户代码可以用方法SystemGCCollect()来强制执行垃圾回收器)

然而大多数情况下我们需要明确地在不执行垃圾回收器的情况下释放托管资源(因为只需要释放一部分但又是非常需要释放的资源但最好不要调用垃圾回收器因为垃圾回收器太浪费系统资源了)或需要释放非托管资源这时候我们该怎么办? 这是我们写代码的时候必须要考虑的问题(垃圾回收器是系统自动实现的一般情况不需要用户干预)否则Windows系统会因为内存耗尽而

现在我来告诉怎么办那就是使用类的Dispose()方法释放所有类型资源 和 使用析构方法释放非托管资源!

Dispose()方法

要通过Dispose()方法来释放资源那么在类定义的时候执SystemIDisposable接口然后在类中必须包含这样定义的方法void Dispose() (在Dispose()方法中就是用户自己写的释放资源的代码段)这样一来用户就会知道可以通过人为地调用Dispose()方法来释放资源 不过需要注意的是垃圾回收器并不是通过调用Dispose()方法来释放托管资源的!

析构方法

在C#中定义析构方法的格式是 ~CLASS_NAME() 非常需要注意的是如果一个类中没有使用到非托管资源那么请一定不要定义析构方法这是因为对象执行了析构方法那么垃圾回收器在释放托管资源之前要先调用析构方法然后第二次才真正释放托管资源这样一来两次删除动作的花销比一次大多的! (不过即使你在类中已经定义了析构方法仍然有办法屏蔽这将在后面的代码范例中说明) 在析构方法中就是用户自己写的释放非托管资源的代码段

下面使用一段代码来示范Dispose()方法和析构方法如何使用:

public class ResourceHolder : SystemIDisposable

{

public void Dispose()

{

Dispose(true);

SystemGCSuppressFinalize(this);

// 上面一行代码作用是防止垃圾回收器调用这个类中的方法

// ~ResourceHolder()

// 为什么要防止呢? 因为如果用户记得调用Dispose()方法那么

// 垃圾回收器就没有必要多此一举地再去释放一遍非托管资源

// 如果用户不记得调用呢就让垃圾回收器帮我们去多此一举吧 ^_^

// 你看不懂我上面说的不要紧下面我还有更详细的解释呢!

}

protected virtual void Dispose(bool disposing)

{

if (disposing)

{

// 这里是清理托管资源的用户代码段

}

// 这里是清理非托管资源的用户代码段

}

~ResourceHolder()

{

Dispose(false);

}

}

上面的代码是一个典型的有两种Dispose方法的类定义

NET Framework中有很多系统类是用这种方法定义Dispose()方法的例如:

MSDN中SystemDrawingBrushDispose方法就是这样定义的:

************************************************************

* 释放由此 Brush 对象使用的所有资源 *

* public void Dispose() *

* 该成员支持 NET 框架结构因此不适用于直接从代码中使用 *

* protected virtual void Dispose(bool); *

************************************************************

这里我们必须要清楚需要用户调用的是方法Dispose()而不是方法Dispose(bool)然而这里真正执行释放工作的方法却并不是Dispose()而是Dispose(bool) ! 为什么呢?仔细看代码在Dispose()中调用了Dispose(true)而参数为true作用是清理所有的托管资源和非托管资源;大家一定还记得我前面才说过使用析构方法是用来释放非托管资源的那么这里既然Dispose()可以完成释放非托管资源的工作还要析构方法干什么呢? 其实析构方法的作用仅仅是一个备份!

为什么呢?

严格地说凡执行了接口IDisposable的类那么只要程序员在代码中使用了这个类的对象实例那么早晚得调用这个类的Dispose()方法同时如果类中含有对非托管资源的使用那么也必须释放非托管资源! 可惜如果释放非托管资源的代码放在析构方法中(上面的例子对应的是 ~ResourceHolder() )那么程序员想调用这段释放代码是不可能做到的(因为析构方法不能被用户调用只能被系统确切说是垃圾回收器调用)所以大家应该知道为什么上面例子中清理非托管资源的用户代码段是在Dispose(bool)中而不是~ResourceHolder()中! 不过不幸的是并不是所有的程序员都时刻小心地记得调用Dispose()方法万一程序员忘记调用此方法托管资源当然没问题早晚会有垃圾回收器来回收(只不过会推迟一会儿)那么非托管资源呢?它可不受CLR的控制啊!难道它所占用的非托管资源就永远不能释放了吗? 当然不是!我们还有析构方法呢! 如果忘记调用Dispose()那么垃圾回收器也会调用析构方法来释放非托管资源的!(多说一句废话如果程序员记得调用Dispose()的话那么代码SystemGCSuppressFinalize(this);则可以防止垃圾回收器调用析构方法这样就不必多释放一次非托管资源了) 所以我们就不怕程序员忘记调用Dispose()方法了

所以我说了这么一大堆的理由综合起来只有两点:

*程序员们啊千万不要忘记调用Dispose()方法! (如果有的话 ^_^)

*万一忘记不要着急还有救!!! 因为还有垃圾回收器帮我们自动调用析构方法!

上一篇:C#高级编程读写文本文件实例

下一篇:c#中Split函数的使用