C#.Net中的非托管代码清理
帮助其它项目组Review代码过程,发现有些地方实现了IDispose接口,同时也发现了一些关于IDispose的问题:
1.A类型实现了IDispose接口,B类型里面含有A类型的字段,B类型没有实现IDispose接口
2.一个类里面实现了Finalize终结器,同时也实现了IDispose接口,但在Dispose方法里面没有调用GC.SuppressFinalize(this)方法.
下面我对以上两个问题分别分析一下,并提出解决方案。
问题1
如果A类型里面有非托管资源需要在实现的IDispose接口里面释放,由于B类型没有实现IDispose接口,B类型的使用者要想释放A类型的非托管资源并不方便.这样的话,就有可能忘记了释放A类型的非托管资源.
解决方案:
实现B类型的IDispose接口,在Dispose方法里面调用A类型的Dispose方法.这样,B类型的使用者在调用B类型Dispose的同时,就把A类型的Dispose也调用了.
问题2
在Dispose方法里面没有调用GC.SuppressFinalize(this)方法,会有什么问题呢,这样会导致垃圾回收器不能对 这个类型的对象及时回收. 当GC开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个垃圾队列当中。GC会调用一个新线程来执行这些对象的 终结器。当终结器执行完毕后,这个对象会从队列中被移除。这个对象在队列中移除之后,当GC再次开始工作的时候,这个对象才能够被回收,所以有终结器的对 象会比没有的在内存中保留更长的时间。在后面我会对这里再详细的描述一下.
解决方案:
在Dispose方法中调用GC.SuppressFinalize(this)方法.这样的话,就不会把有终结器的对象则添加到垃圾队列当中.
切入正题
.net中,非托管代码清理有两种方式:Finalize方式和Dispose方式.
Finalize方式:通过对自定义类型实现一个Finalize方法来释放非通过资源.
从.net2.0开始,C#编译器不能对Finalize进行显示的调用和重写,必须使用析构函数来实现它.
class A
{
~A()
{
释放资源;
}
}
上面的代码就是通过Finalize方式来释放资源的跟C++用析构函数释放资源的代码很象.
但是它实现方式和C++不同,因为它是由垃圾回收器来管理内存的.
大家看到了,用Finalize方式释放非托管资源很简单,但是如果你了解了他的实现方式,你可能就不会选择用它来释放非托管资源.
那Finalize方式在.net内部是如何实现的呢?
当GC(垃圾回收器)开始工作的时候,它首先将没有终结器的垃圾对象从内存中移除,有终结器的所有对象则添加到一个终止化队列当中。GC会调用一个 新线程来执行这些对象的终结器。当终结器执行完毕后,这些对象会从队列中被移除。这时候由于这些对象在第一次检测到的时候没有被释放,它们将会进入第1代 对象,直到GC检测到第0代对象和第1代对象再次充满时,这时候GC才会把刚才那些对象释放掉,所以有终结器的对象会比没有的在内存中保留更长的时间。
提示:垃圾回收器把托管堆中的对象分为3代,分别是0,1,2.一般分配为:0代约256K,1代约是2MB,第2代约是MB,代龄越高,容量就越 大,显然效率也就越低.首先被添加到托管堆中的对象被定为第0代,当第0代充满时,就会执行垃圾回收,未被回收的对象代领将提升1代.
由于以上原因应该避免仅使用Finalize方式释放非托管资源.
Dispose模式:在自定义类中实现IDispose接口,在接口中的Dispose方法中对非托管资源进行释放.闲话少说,上代码
public class MyResourceRelease: IDisposable
{
/// 保证资源只用释放一次
private bool _alreadyDisposed = false;
/// 用来判断释放资源的类别(托管和非托管)
protected virtual void Dispose(bool isDisposing)
{
if(_alreadyDisposed)
{
return;
}
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_alreadyDisposed = true;
}
public void Dispose()
{
Dispose(true);
}
}
上面的代码就是用Dispose方式释放资源的方法.因为上面自定义的Dispose(bool isDisposing)方法是virtual的,所以还可以在派生类里面对它进行override
public class MyDerivedResource: MyResourceRelease
{
private bool _disposed = false;
protected override void Dispose(bool isDisposing)
{
if(_disposed)
{
return;
}
try
{
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_disposed = true;
}
finally
{
base.Dispose(isDisposing);
}
}
}
这样可以确保释放继承链上所有对象的引用资源,在整个继承层次中传播Dispose模式.
那用Dispose方式非托管资源就是最好的方法了吗?
其实不然,因为类型实现了IDispose接口,这个类的使用者必须显示调用Dispose方法,或者在创建该类型对象的时候使用using关键 字,对于一些粗心的使用者可能会忘记调用Dispose方法,或者没有使用using关键字,这样就导致了非托管资源没有释放的后果.
最佳方案
同时实现终结器和Dispose方式.这样对于细心的使用者直接显示调用Dispose方法会提高垃圾回收的性能,对于粗心的使用者虽然忘记了调用Dispose方法,但也不至于使得非托管资源得不到释放.
注意这里用到了GC. SuppressFinalize(this)方法.
代码如下:
public class MyResourceRelease: IDisposable
{
~MyResourceRelease()
{
Dispose(false);
}
/// 保证资源只用释放一次
private bool _alreadyDisposed = false;
/// 用来判断释放资源的类别(托管和非托管)
protected virtual void Dispose(bool isDisposing)
{
if(_alreadyDisposed)
{
return;
}
if(isDisposing)
{
//释放托管资源
}
//释放非托管资源
_alreadyDisposed = true;
}
public void Dispose()
{
Dispose(true);
//阻止GC把该对象放入终结器队列
GC.SuppressFinalize(this);
}
}
C#.Net中的非托管代码清理的更多相关文章
- Android内存泄漏第二课--------(集合中对象没清理造成的内存泄漏 )
一.我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大.如果这个集合是static的话,那情况就更严重 ...
- VS中生成、清理项目、调试、開始运行(不调试)、Debug 和 Release等之间的差别
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/helloUSB2010/article/details/35802437 一.生成和又一次生成 &q ...
- Windows 8.1 "计算机" 中文件夹清理
计算机 win8.1 也叫这台电脑 清理文件夹 保留磁盘分区图标 注册表清理 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ ...
- [PHP] 存储改造中的逻辑和清理遗留的问题
现象:用户读信时,根据路径的哈希结果,访问四台服务器中一台请求文件,这四台缓存机器已经下线,访问不到再去后端存储访问浪费了时间 前因:每一封信都是一个文件,存储在公司内部的分布式文件系统s3上.因为读 ...
- 【转载】VS中生成、清理项目、调试、开始执行(不调试)、Debug 和 Release等之间的区别
https://blog.csdn.net/u012441545/article/details/51404412
- pinpoint体系中,关于如何清理过期hbase数据
版本: pinpoint:1.7.1 hbase:1.2.6 命令行命令: $HBASE_HOME/bin/hbase shell newrestruct.hbase 备注:保留一天半的数据(秒 ...
- Angularjs中的缓存以及缓存清理
写在最前面:这篇博文是2篇文章组成,详细介绍了Angularjs中的缓存以及缓存清理,文章由上海尚学堂转载过来,欢迎大家阅读和评论.转载请注明出处,谢谢! 一个缓存就是一个组件,它可以透明地储存数据, ...
- kylin的clube合并后清理hbase中产生的相关历史表
kylin的clube合并后清理hbase中产生的相关历史表 kylin 的clube 历史的每次构建,都会产生一个hbase的表:虽然可以设置按照一定策略合并,但是合并后hbase 历史表不会被自动 ...
- [原创]mybatis中整合ehcache缓存框架的使用
mybatis整合ehcache缓存框架的使用 mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓 ...
随机推荐
- Android内存控制小技巧-使用矢量图来节省你的内存并简化你的开发。
先上一个 位图和矢量图的 说明.http://zhidao.baidu.com/link?url=xwvs5CBzWeh15O3Ee4bICwCqg4PCQWwg5oZ0a6CVydbVZzufqrI ...
- Code Hard or Go Home
介绍Webkit的渊源 http://hypercritical.co/2013/04/12/code-hard-or-go-home
- 【转】为drupal初学者准备的12个精品课程
下面是一些网上免费的drupal教程,这些教程将对初学者和那些从别的CMS转向drupal的开发者非常有帮助.初级教程 1.在开始用drupal之前,你要知道一些基本的东西,内容很简单,但有些还是值得 ...
- CentOS7 安装 swoole
sudo pecl install swoole 即可安装.安装完后修改php.ini,加入extension=swoole.so 重启 sudo systemctl restart php-fpm ...
- Chapter7:类
关于this指针 成员函数通过一个名为this的额外的隐式参数来访问调用它的对象.当我们调用一个成员函数时,用请求该函数的对象初始化this. total.isbn(); //等价于编译器重写为 Sa ...
- [转]32位和64位系统区别及int字节数
一)64位系统和32位有什么区别? 1.64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存 2.64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会 ...
- [转] Web前端优化之 CSS篇
原文链接: http://lunax.info/archives/3097.html Web 前端优化最佳实践第四部分面向 CSS.目前共计有 6 条实践规则.另请参见 Mozilla 开发者中心的文 ...
- MySql数据备份与恢复小结
方法1 备份 .sql" FROM 表名; 恢复 .sql" INTO TABLE 表名; 补充几个设置 1. FIELDS TERMINATED BY ',' #字段间隔符2. ...
- 王家林的“云计算分布式大数据Hadoop实战高手之路---从零开始”的第十一讲Hadoop图文训练课程:MapReduce的原理机制和流程图剖析
这一讲我们主要剖析MapReduce的原理机制和流程. “云计算分布式大数据Hadoop实战高手之路”之完整发布目录 云计算分布式大数据实战技术Hadoop交流群:312494188,每天都会在群中发 ...
- 基于gSOAP使用头文件的C语言版web service开发过程例子
基于gSOAP使用头文件的C语言版web service开发过程例子 一服务端 1 打开VS2005,创建一个工程,命名为calcServer. 2 添加一个头文件calc.h,编辑内容如下: 1// ...