IDisposable的接口是用来手动释放非托管资源,换另一句话说,释放非内存(非托管)资源。内存资源因为垃圾回收保证回收,但非内存资源(如socket, 文件handle等)的数量比内存少,占用多也不一定触发垃圾回收,因此需要手动及时清理。基本上在.NET中,如果一个类使用了非内存资源,它都会实现一个IDisposable接口。如果不手动调用Dispose方法,那么当这个类对象被垃圾回收时,.NET会调用类对象的析构方法,通知类对象释放非内存资源。所以垃圾回收可以保证非托管资源的回收,但时效性差。一个占用socket的对象要等垃圾回收机制来回收socket资源,那么系统的吞吐量肯定差。从上面的描述可以看出来,非托管资源的回收时机有两个,一个是手动(IDisposable接口),一个是自动(垃圾回收调用的析构器)。
4常见内存问题
4.1使用了非托管资源的类
非托管资源的类是指本身是被CLR管理的,而且其管理的非托管资源也可以被CLR自动回收,因为CLR只能跟踪非托管资源的生存期,但是不能主动去做GC,所以GC的时机不确定,所以在使用完后应及时释放。
例如:调用FileStream
FileStream file = new FileStream(@"c:\Test.txt", FileMode.Open);
连续两次调用程序会报“文件正在使用中”的异常,如果两次调用中间调用强制回收,则不会报异常。
再例如:使用ODP.NET
的OracleCommand
和OracleDataReader
,在Close
后还需要Dispose
;
OracleCommand cmd = new OracleCommand();
cmd.CommandText = sbSQL.ToString();
cmd.Connection = conn;
cmd.Parameters.Add(p1);
OracleDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{
//…
dr.Close();
}
else
{
}
dr.Dispose();
cmd.Dispose();
常见的使用了非托管资源的类如下:
一 | 二 | 三 |
---|---|---|
ApplicationContext | Component | ComponentDesigner |
Brush | Container | Context |
Cursor | FileStream | DataSet |
Font | Icon | Image |
Matrix | Texture | OdbcDataReader |
OleDBDataReader | Pen | Regex |
Socket | StreamWriter | Timer |
Transaction | DataReader | Ping |
Tooltip | Bitmap | SerialPort |
以上列出的类均继承了IDisposable
接口,需要在使用完后调用Dispose
方法释放或者使用Using
语句块,比如DataTable
、DataSet
、DataReader
、Transaction
、BitMap
…
4.2 Win32API及COM
指通过本地API函数与托管对象进行交互(比如:通过 P/Invoke方式调用本地DLL,DLLImport
声明静态外部函数和COM Interop)所用到的非托管资源。
例如:当通过DLL Import调用 API函数GetDC函数时忘了调用ReleaseDC
去释放设备句柄造成4个字节的内存泄漏。
再如:智能文档中使用的Word
以及导出EXCEl
功能用到的Office
的COM非托管组件,在关闭时GC不能识别COM组件而造成有时候无法对COM对象进行释放,这时候可以通过以下两个InteropServices
函数进行释放
System.Runtime.InteropServices.Marshal.ReleaseComObject(comObject);
递减与指定的 COM 对象关联的指定 运行时可调用包装 (RCW) 的引用计数。
返回值为关联的 RCW 的引用计数的新值。此值通常为零,因为无论调用包装 COM 对象的托管客户端有多少,RCW 仅保留对该对象的一次引用。
所以通过这个方法显式的通过CLR释放非托管 COM 对象上的所有引用。
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(comObject);
通过将 运行时可调用包装 (RCW) 的引用计数设置为 0,释放对它的所有引用。
返回值为与 comObject参数关联的 RCW 的引用计数的新值,如果释放成功,则为 0(零)。
4.3事件造成的内存泄漏
当不需要使用事件时,应退订事件,为了确保安全可以在Dispose
方法中退订事件.
当对象不再触发事件时,应该将对象设为null
来移除所有的事件订阅者
4.4动态添加生成控件造成内存泄漏
动态生成引用了非托管资源的控件后,注意一定要Dispose();
例如:
RichTextBox rtb = new RichTextBox();
frm.Controls.Add(rtb);
frm.Controls.Remove(rtb);
rtb.Dispose();