释放非托管资源

在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源.

托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.net运行库在合适的调用垃圾回收器进行回收.

非托管资源指的是.net不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管资源,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源.

在.net中,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数中.

注意,不能再析构函数中释放托管资源,因为析构函数是由垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结构.

本来呢,如果按照上面做法,非托管资源也能够由垃圾回收器进行回收,但是非托管资源一般是有限的,比较宝贵的,而垃圾回收器是由CLR(不知道CLR的看我上一篇文章)自动调用的,这样就无法保证一级的释放非托管资源,因此定义了一个Dispose()方法,让使用者能够手动的释放非托管资源.Dispose()方法是防雷的托管资源和非托管资源,使用者手动调用此方法后,垃圾回收期不会对此类实例再次进行回收.Dispose()方法是有使用者调用的,在调用时,类的托管资源和非托管资源肯定都还没有被回收,所以可以同时回收这两者资源.

微软为非托管资源的回收专门定义了一个接口:IDisposable,接口中只能包含一个Dispose()方法.任何包含非托管资源的类,都应该继承此接口.

在一个包含非托管资源的类中,关于资源释放的标准做法是:

(1)  继承IDisposeable接口;

(2) 事项Dispose()方法,在其中释放托管资源和非托管资源,并将对象本身从垃圾回收器中移除(垃圾回收器不在回收此资源);

(3) 实现类析构函数,在其中释放非托管资源

在使用的时候,显示调用Dispose()方法,可以及时的释放资源,同时通过移除Finalize()方法的执行,提高了性能;如果没有显示调用Dispose()方法,垃圾回收器也可以通过析构函数来释放非托管资源,垃圾回收器本身就具有回收托管资源的功能,从而保证资源的正常释放,只不过由垃圾回收器回收会导致非托管资源的未能及时释放.

在.net中应该尽可能的少使用析构函数释放资源.在没有析构函数的对象在垃圾处理器一次处理中从内存删除,但有析构函数的对象,需要两次,第一次调用析构函数,第二次删除对象.而且在析构函数中包含大量的释放资源代码,会降低垃圾回收器的工作项链,影响性能.所以对于包含非托管资源的对象,最好及时的调用Dispose()方法来回收资源,而不是依赖垃圾回收器.

上面就是.net中对包含非托管资源的类的资源释放几只,只要按照上述的步骤编写代码,累就属于资源安全的类.

案例:

class MyResourceWrapper:IDisposable

{

public void Dispose()

{

Console.WriteLine("release resources with Dispose");

Console.Beep();

}

}

class Program

{

static void Main(string[] args)

{

MyResourceWrapper mr = new MyResourceWrapper();

mr.Dispose();

}

}

当我们显示调用Dispose方法的时候,可以听到系统的蜂鸣声.

注意:通多Dispose进行资源的释放也是有潜在的风险的,因为Dispose方法需要被程序员显示的滴啊用,如果代码中漏掉了Dispose的调用或者在Dispose调用之前产生了异常从而没有指定Dispose,那么有些资源可能就一直留在内存中了.

所以我们应该使用下面的方式保证Dispose方法可以被调用到:

static void Main(string[] args)

{

MyResourceWrapper mr = new MyResourceWrapper();

try

{

//do something wiht mr object

}

finally

{

mr.Dispose();

}

}

但是,每次编写Idspose的代码都是用try块会觉得很麻烦,还好C#中,我们可以重用using关键字来简化Dispose的调用.

重用using关键字

在C#中,using语句提供了一个高效的调用对象Dispose方法的方式.对于任何IDisposable接口的类型,都可以使用using语句,而对于那些没有实现IDisposable接口的类型,使用using语句会导致一个编译错误.

static void Main(string[] args)

{

using (MyResourceWrapper mr = new MyResourceWrapper())

{

//do something with mr object

}

}

在using语句块结束的时候,mr实例的Dispose方法会被自动调用,using语句不仅免除了程序员输入Dispose调用的代码,他还保证Dispose方法被调用,无论using语句块是否顺利执行. 事实上,C#编译器为using语句自动添加了try/finally语句块.

.NET提供了两种释放非托管资源的方式,一种是Finalize方法和Dispose方法.

接下来再来看Finalize方法

在.net的基类System.Object中,定义了名为Finalize()的虚方法,这个方法默认什么都不做.我们可以为自定义的类型重写Finalize方法,在该方法中加入必要的非托管资源清理逻辑.当要从内尊中删除这个类型的对象时,垃圾回收器会调用对象的Finalize方法.所以,无论.net进行一次自发的垃圾回收,还是通过GC.Collect()进行强制垃圾回收,Finalize方法总是会被调用.另外,当承载应用程序的AppDomain从内存中移除时,同样会调用Finalize方法.

重写Finalize方法

假设我们现在有一个是由非托管资源的类型,我们就需要重写Finalize方法来进行非托管资源的清理,但是当通过下面的方式重写Finalize方法的时候,会出现编译错误:

class MyResourceWrapper

{

protected override void Finalize()

{ }

}

其实,当我们想要重写Finalize方法时,C#为我们提供了(类似C++)析构函数语法(C#终结器)来重写该方法.C#终结器和构造函数语法类似,方法名和类型名一样;不同的是,终结器具有~前缀,并且不能使用访问修饰符,不接受参数,不能重载,所以一个类只能有一个终结器.

class MyResourceWrapper

{

~MyResourceWrapper()

{

Console.WriteLine("release unmanaged resources");

Console.Beep();//t通过控制台播放器播放提示音

}

}

之所以C#只支持这种方式进行Finalize方法的重写,是因为C#编译器回味Finalize方法隐式的加入一些必要的基础代码.添加的那些基础代码我就不给你们看了,反正就是保证finalize方法总是能被执行.

当我们执行下面的代码的时候,我们就可以听到蜂鸣声了:

MyResourceWrapper mr = new MyResourceWrapper();

Finalize的工作机制

他的工作机制很复杂,想要深入研究,您可以自己上网查看(估计没人查),我只是参考网上一些比较简单的说法来理解Finalize的工作机制.

其实,Finalize方法的调是相当耗费资源的,Finalize方法的作用是保证.net对象能够在垃圾回收时清零非托管资源,如果创建了一个不使用非托管资源的类型,实现终结器是没有任何作用的,所以,如果没有特殊的需要应该避免重写Finalize方法.

Dispose和Finalize的结合

Finalize可以通过垃圾回收进行自动的调用,而Dispose需要被代码显示的调用.so,为了保险起见,对于一些非托管资源,还是有必要实现终结器的(什么是终结器呢,看最后),也就是说,如果我们忘记了显式的调用Dispose,那么垃圾回收也会调用Finalize,从而保证非托管资源的回收.

在MSND中提供了一种很好的模式来实现IDisposable接口来结合Dispose和FInalize,案例如下:

class MyResourceWrapper:IDisposable

{

private bool IsDisposed = false;

~MyResourceWrapper()

{

Dispose(false);

}

public void Dispose(bool disposing)

{

if (!this.IsDisposed)

{

if (disposing)

{

//清除托管资源

}

//清除非托管资源

}

this.IsDisposed = true;

}

public void Dispose()

{

Dispose(true);

//告诉GC不调用Finalize方法

GC.SuppressFinalize(this);

}

}

在这个模式中,void Dispose(bool disposing)函数通过一个disposing参数来区别当前是否是被Dispose()调用.如果是被Dispose()调用.如果是被Dispose()调用,那么需要同时释放托管和非托管资源.如果是被终结器调用了,那么只需要释放非托管的资源即可.Dispose()函数是被其他代码显式调用应要求释放资源的,而Finalize是被GC调用的.

另外,由于在Dispose()中已经释放了托管和非托管的资源,因此在对象中被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用GC.SuppressFinalize(this)避免重复调用Finalize.同样,因为IsDisposed变量的存在,资源只会被释放一次,多余的调用会被忽略.

这个模式的优点如下:

1.如果没有显式的调用Dispose(),未释放托管和非托管资源,那么在垃圾回收时,还会执行Finalize,释放非托管资源,同时GC会释放托管资源.

2.如果调用了Dispose(),就能及时释放托管和非托管资源,那么该对象被垃圾回收时,就不会执行Finalize(),提高了非托管资源的使用效率并提升了系统性能.

终结器(finalizer)主要用于C#.Net中的非托管代码清理中,通常同时实现终结器和Dispose方式。这样对于细心的使用者直接显示调用Dispose方法会提高垃圾回收的性能,对于粗心的使用者虽然忘记了调用Dispose方法,但也不至于使得非托管资源得不到释放。代码如下:

C#编程(七十四)----------释放非托管资源的更多相关文章

  1. C# 释放非托管资源

    C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放非托管资源的代码(释非 ...

  2. 6.C# 释放非托管资源2

    C# 释放非托管资源 C#中资源分为托管资源和非托管资源. 托管资源由垃圾回收器控制如何释放,不需要程序员过多的考虑(当然也程序员也可以自己释放). 非托管资源需要自己编写代码来释放.那么编写好的释放 ...

  3. 5.C#释放非托管资源1

    释放非托管资源 在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源. 托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托 ...

  4. Dispose模式释放非托管资源

    实现方式用的是设计模式里的模板模式,基类先搭好框架,子类重写void Dispose(bool disposing) 即可. 需要注意的是基类的Finalize函数也就是析构函数调用的是虚函数void ...

  5. [转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源:

    托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收. 非托管资源指的是.NET不知道如何回 ...

  6. IDisposable?释放非托管资源接口

    原文:https://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html IDisposable高级篇:https://docs.micro ...

  7. .net 资源释放(托管资源和非托管资源)

    1.托管资源 像int.float.DateTime等都是托管资源:net中80%的资源都是托管资源: 托管资源的回收通过GC(垃圾回收器)自动释放分配给该对象的内存,但无法预测进行垃圾回收的时间,我 ...

  8. C# 托管非托管资源释放

    1.C#几乎所有对象都为托管对象,不同点是有的对象封装了非托管资源. 2.C#大部分对象在进行垃圾回收时都可以回收,包括非托管资源,因为非托管资源都已经通过C#类进行了封装,会将非托管资源的释放放在析 ...

  9. 【读书笔记】C#高级编程 第十四章 内存管理和指针

    (一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64 ...

随机推荐

  1. mysql及linux发行版下载源

    MySQL国内镜像资源 搜狐开源镜像站:http://mirrors.sohu.com/ 国内开源镜像站点汇总 http://segmentfault.com/a/1190000000375848   ...

  2. Hibernate 二级缓存疑难点

    一级缓存:缓存实体 二级缓存:缓存实体 Hibernate查询缓存缓存的是查询出来的实体的部分属性结果集和实体的ID(注意这里不是实体). Hibernate查询缓存:对List起作用.但是Hiber ...

  3. (windows下)安装mysql

    一.先从mysql的官网上下载对应版本的mysql zip包(适用于windows下的) 二.解压zip包放到自定义的文件夹下(我放的是e盘,路径为E:\mysql-5.6.40-winx64) 三. ...

  4. stylus项目知识点

    1.在项目中,引入.sty文件的时候,用来下面方式 @import "~common/stylus/variable.styl" ~ 是stylus的写法,参考https://gi ...

  5. synchronized和lock

    Synchronized  同步代码块 使用 monitorenter 和 moniterexit 指令实现, monitorenter指令插入到同步代码块的开始位置, moniterexit 指令插 ...

  6. k8s的imagePullSecrets如何生成及使用

    如果公司的docker仓库(harbor),需要用户认证之后,才能拉取镜像. 那如何在k8s里生成这个secret呢? 这个secret如何还原呢? 在k8s的yaml文件里如何实现呢? 这里提供几个 ...

  7. 迷茫于Hibernate/JPA的人提一些建议。

    想对那些“迷惑”于Java ORM框架的J2EE开发人员提一些建议,希望能够对他们 更深入的理解和运用J2EE ORM框架来提速工作有所帮助,这些建议可能显得有些”陈旧“和”肤浅“, 因为最近半年我没 ...

  8. Codeforces Round #257 (Div. 1) D - Jzzhu and Numbers 容斥原理 + SOS dp

    D - Jzzhu and Numbers 这个容斥没想出来... 我好菜啊.. f[ S ] 表示若干个数 & 的值 & S == S得 方案数, 然后用这个去容斥. 求f[ S ] ...

  9. Qt5编译oracle驱动教程

    我们都知道oracle数据库的强大,并且好多企业或者教学用到数据库时都会推荐使用.但是Qt因为版权问题没有封装oracle数据库专用驱动,网上也有一大堆说法和教程,但是或多或少的都有问题.下面废话不多 ...

  10. JAVA 图形界面开发基础详解

    与C的win32一样,JAVA也有自己的图形界面开发,将在此篇博客中对基础部分进行讲解. 1.Java提供的图形界面类有哪些? Java提供了两套图形界面 (1)AWT组建(基础) AWT组件是jdk ...