IDisposable 接口

托管资源和非托管资源

  • 托管资源

    • CLR 控制和管理的内存资源,如程序中在 Heap 上分配的对象、作用域内的变量等;
    • GC 机制实现自动内存管理和托管堆的全权管理;
  • 非托管资源
    • CLR 不能控制管理的部分,如文件流Stream/数据库连接coonection/窗口句柄/组件COM等;
    • Finalize 方法(析构函数) GC 隐式自动调用,Dispose 方法手动强制显式调用;
    • 尽量避免使用 Finalize() 方法清理资源,推荐实现 Dispose() 方法供显式调用;

注:MSDN - 实现 Finalize() 方法或析构函数对性能可能会有负面影响。用 Finalize() 方法回收对象占用的内存至少需要两次垃圾回收,第一次调用析构函数,第二次删除对象。 GC 机制在回收托管对象内存之前,会先调用对象的析构函数。   

析构函数(Finalize方法) .vs. Dispose方法

Finalize 方法用于释放非托管资源,Dispose 方法用于清理或释放由类占用的非托管和托管资源。IDisposable 接口定义见上,自定义类应实现 IDisposable 接口,设计原则:

  • 可以重复调用 Dispose() 方法;
  • 析构函数应该调用 Dispose() 方法;
  • Dispose() 方法应该调用 GC.SuppressFinalize() 方法,指示垃圾回收器不再重复回收该对象;

在一个包含非托管资源的类中,资源清理和释放的标准模式是:

  1. 继承 IDisposable 接口;
  2. 实现 Dispose() 方法,在其中释放托管和非托管资源,并将对象从垃圾回收器链表中移除;
  3. 实现类的析构函数,在其中释放非托管资源;

其中,变量 "isDisposing" 来区分手动显式调用(true)还是GC隐式调用(false)。

  public class MyDispose : IDisposable
{
public MyDispose() { }
~MyDispose() {
Dispose(false);
} private bool isDisposed = false;
public void Dispose(){
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing) // 子类可重写
{
if (false == this.isDisposed)
{
if (true == isDisposing){
OtherManagedObject.Dispose(); // 释放托管资源 ...
}
OtherUnManagedObjectDisposeOrClose(); // 释放非托管资源 ...
this.isDisposed = true;
}
}
}

析构函数执行在类的实例被销毁之前需要的清理或释放非托管资源的行为,注意不能在析构函数中释放托管资源。类的析构函数被编译后自动生成 protected void Finalize() 方法,GC 垃圾回收时会调用该方法并对继承链中的所有实例递归地调用 Finalize() 方法。

Object.Finalize() 方法不可重写。

  • 类的析构函数不可继承和重载、不能带访问修饰符,一个类至多有一个析构函数;
  • 析构函数只针对类的实例对象,没有静态析构函数;
 protected void Finalize(){
try{
//
}
finally{
base.Finalize();
}
}

Finalize() 方法被调用的情况:

  • 显式调用System.GC 的 Collect方法(不建议);
  • Windows 内存不足、第G0代对象充满;
  • 应用程序被关闭或 CLR 被关闭;

Dispose() 方法的调用分 2 种:

  • 使用 using 语句会自动调用:using( MyDispose myObj = new MyDispose() ) {…}
  • 显式调用:myObj.Dispose();

一个资源安全的类,都应实现 IDisposable 接口和析构函数,提供手动释放资源和系统自动释放资源的双保险。(1)若一个类A有一个实现了 IDisposable 接口类型的成员并创建(创建而不是接收,必须是由类A创建)它的实例对象,则类A也应该实现 IDisposable 接口并在 Dispose 方法中调用所有实现了 IDisposable 接口的成员的 Dispose 方法;(2)如果基类实现了 IDisposable 接口,那么其派生类也要实现 IDisposable 接口,并在其 Dispose 方法中调用基类中 Dispose 方法;只有这样才能保证所有实现了 IDisposable 接口的类的对象的 Dispose 方法能被调用到、手动释放任何需要释放的资源。

参考

为什么 IEnumerator 接口没有继承 IDisposable 接口
托管资源和非托管资源IDisposable接口的一个典型例子
Finalize - Dispose - SuppressFinalizeIDisposable和Finalize的区别和联系
对.Net 垃圾回收 Finalize 和 Dispose 的理解
深刻理解 C# 中资源释放

GC 垃圾回收

本质:跟踪所有被引用到的对象,整理不再被引用的对象并回收相应内存。

优点

  • 减少由于内存运用不当产生的Bug,降低编程复杂度;
  • 高效的内存管理;
  • 提高软件系统的内聚;

代 Generation

NET 垃圾回收器将 CLR 托管堆内的对象分为三代:G0、G1、G2,代龄机制支持有选择地查询,提高垃圾回收性能,避免回收整个托管堆。

  • G0:小对象(Size<85000Byte),最近被分配内存的对象,支持快速存取对象;
  • G1:在GC中幸存下来的G0对象,CLR 检查过一次未被回收的G0对象;
  • G2:大对象(Size>=85000Byte),CLR 检查过二次及以上仍未被回收的G1/G2对象;

通过 GC.GetGeneration() 方法可以返回对象所处的代。当第0代对象已满时,自动进行垃圾回收,第0代中未被释放的对象成为第1代,新创建的对象成为第0代,以此类推,当第0代再次充满时会再次执行垃圾回收,未被释放的对象被添加到第1代。随着程序的执行,第1代对象会产生垃圾,此时垃圾回收器并不会立即执行回收操作,而是等第1代被充满回收并整理内存,第1代中未被释放的对象成为第2代。当第1代收集时,第0代也需要收集,当第2代收集时,第1和第0代也需要收集。

根 root

每个应用程序都包含一组根,每个根都是一个存储位置,包含一个指针或引用托管堆上的一个对象或为null,由 JIT编译器 和 CLR运行时 维护根(指针)列表。

工作原理

基于代的垃圾回收器如下假设:

  • 对象越新,生存期越短,最近分配内存空间的对象最有可能被释放,搜索最近分配的对象集合有助于花费最少的代价来尽可能多地释放内存空间;
  • 对象越老,生存期越长,被释放的可能性越小,经过几轮GC后,对象仍然存在,搜索代价大、释放内存空间小;
  • 程序的局部性原理 :同时分配的内存对象通常同时使用,将它们彼此相连有助于提高缓存性能和回收效率;
  • 回收堆的一部分速度快于回收整个堆;

标记和清除 (Mark & Sweep) 收集算法:避免出现 "环引用" 造成内存泄露
利用内部结构的 终止队列(Finalization Queue) 跟踪保存具有 Finalize 方法(定义了析构函数)的对象。

  • ReRegisterForFinalize():将对象的指针重新添加到Finalization队列中;(允许系统执行Finalize方法)
  • SuppressFinalize():将对象的指针从Finalization 队列中移除;(拒绝系统执行Finalize方法)

程序创建具有 Finalize 方法的对象时,垃圾回收器会在终止队列中添加一个指向该对象的项(引用或指针)。当对象不可达时,没有定义析构函数的不可达对象直接由 GC 回收,定义了析构函数的不可达对象从终止队列中移除到 终止化-可达队列(F-reachable Queue)中。在一个特殊的专用线程上,垃圾回收器会依次调用该队列中对象的 Finalize 方法并将其从队列中移除,执行后该对象和没有Finalize方法的垃圾对象一样,然后在下一次 GC 中被回收。(GC线程 和 Finalizer线程 不同)
算法分 2 步:

  • 标记阶段:垃圾识别。从应用程序的 root 出发,利用相互引用关系,递归标记(DFS),存活对象被标记,维护一张树图:"根-对象可达图"; 
  • 压缩阶段:内存回收。利用 Compact 压缩算法,移动内存中的存活对象(大对象除外)并修改根中的指针,使内存连续、解决内存碎片问题,有利于提高内存再次分配的速度和高速缓存的性能;  

参考

C#基础知识梳理系列十一:垃圾回收机制步步为营 C# 技术漫谈 四、垃圾回收机制(GC)
垃圾回收机制 - Generation的原理分析
详解 Finalization队列与 F-reachable队列深入浅出理解 GC 机制
垃圾回收GC:.Net自动内存管理系列

内存泄漏

按照编译原理,内存分配策略有3种:

  • 静态存储区(方法区):编译时即分配好,程序整个运行期间都存在,主要存放静态数据、全局static数据和常量
  • 栈区:局部变量,自动释放
  • 堆区:malloc或new的动态分配区,需手动释放

推荐使用 .Net 内存分析工具:CLR Profiler,用来观察托管堆内存分配和研究垃圾回收行为的一种工具。

附注:

该处提供一个狂降内存的方法(摘自网上),可以极大优化程序内存占用。
这个函数是将程序的物理内存尽可能转换为虚拟内存,大大增加硬盘读写,是不好的,慎用!!
使用方法:在程序中用一个计时器,每隔几秒钟调用一次该函数,打开任务管理器

    [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
/// <summary>
/// 释放内存
/// </summary>
public static void ClearMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}

  

C# ~ 由 IDisposable 到 GC的更多相关文章

  1. C# ~ 从 IEnumerable / IEnumerator 到 IEnumerable<T> / IEnumerator<T> 到 yield

    IEnumerable / IEnumerator 首先,IEnumerable / IEnumerator 接口定义如下: public interface IEnumerable /// 可枚举接 ...

  2. C#GC垃圾回收和析构函数和IDisposable的使用

    一,什么是GC 1,GC是垃圾回收器,一般来说系统会自动检测不会使用的对象或变量进行内存的释放,不需要手动调用,用Collect()就是强制进行垃圾回收,使内存得到及时的释放,让程序效率更高. 2,G ...

  3. C#技术漫谈之垃圾回收机制(GC)

    GC的前世与今生 虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久.早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就已经提供了GC的功能,这是 ...

  4. 关于GC和析构函数的一个趣题

    这个有趣的问题感谢装配脑袋友情提供. 请看如下代码: public class Dummy { public static Dummy Instance; ; ~Dummy() { Instance ...

  5. C#中对IDisposable接口的理解

    http://blog.sina.com.cn/s/blog_8abeac5b01019u19.html C#中对IDisposable接口的理解 本人最近接触一个项目,在这个项目里面看到很多类实现了 ...

  6. C# 中正确实现 IDisposable 接口

    作用 此接口的主要用途是释放非托管资源. 当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存. 但无法预测进行垃圾回收的时间. 另外,垃圾回收器对窗口句柄或打开的文件和流等非托管资源一无所知 ...

  7. 从C#垃圾回收(GC)机制中挖掘性能优化方案

    GC,Garbage Collect,中文意思就是垃圾回收,指的是系统中的内存的分配和回收管理.其对系统性能的影响是不可小觑的.今天就来说一下关于GC优化的东西,这里并不着重说概念和理论,主要说一些实 ...

  8. .NET面试题解析(06)-GC与内存管理

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 GC作为.NET的重要核心基础,是必须要了解的.本文主要侧重于GC内存管理中的一些关键点,如要要全面深入了 ...

  9. C# IDisposable接口

    public class MyClass : IDisposable { public int a; public MyClass() { //构造 } public void Dispose() { ...

随机推荐

  1. 从Windows中卸载Apache

    在重装Apache或者妳不再需要它的时候,这时就需要将它卸载. 下面是步骤: 打开开始菜单(win8中ÿ+X)或者我的电脑(废话) 找到并打开Apache的安装目录(Program Files\Apa ...

  2. Senparc.Weixin.MP SDK 微信公众平台开发教程(十五):消息加密

    前不久,微信的企业号使用了强制的消息加密方式,随后公众号也加入了可选的消息加密选项.目前企业号和公众号的加密方式是一致的(格式会有少许差别). 加密设置 进入公众号后台的“开发者中心”,我们可以看到U ...

  3. Linux C/C++的编译

    以前在Linux上面编译过C,但是没有编译过C++,今天用到了,就稍微学习了一下. 简单的介绍 linux 中最重要的编译工具是 GCC.GCC 是 GNU 的 C 和 C++ 编译器.实际上,GCC ...

  4. Java关于流知识总结

    流总结: 一.流的分类: 数据单位:字节流  字符流 方向:  输出流 输入流 角色:  节点流 套节流 字节流:以Stream结尾. 字符流:以Reader 和Writer 结尾. 输入流:所有带有 ...

  5. tomcat 5.5 动态加载类

    转载于:http://www.itxuexiwang.com/a/javadianzishu/tomcat/2016/0225/161.html?1456480735 开发使用的是tomcat5.5. ...

  6. Atitit 作用域的理解attilax总结

    Atitit 作用域的理解attilax总结 1.1. 作用域是指对某一变量和方法具有访问权限的代码空间, 1 1.2. 作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突.1 1.3 ...

  7. Ecshop :后台添加新功能 菜单及 管理权限 配置

    需求:在<商品管理>下增加一项[商品推广管理]功能 一. 添加菜单项 打开 /admin/includes/inc_menu.php 文件(后台框架左边菜单),在最后添加一行如下: $mo ...

  8. Java EE开发平台随手记6——Mybatis扩展4

    这篇博客中来说一下对Mybatis动态代理接口方式的扩展,对于Mybatis动态代理接口不熟悉的朋友,可以参考前一篇博客,或者研读Mybatis源码. 扩展11:动态代理接口扩展 我们知道,真正在My ...

  9. 初了解JS设计模式,学习笔记

    什么是设计模式. 回答这个问题,往往我们得先知道我们为什么需要设计模式,正是因为有需求才会有设计模式,难道不是吗? 我们为什么需要设计模式. 如果没有按照设计模式去写,你的代码很可能是乱无肆忌写的,也 ...

  10. 使用Source Safe for SQL Server解决数据库版本管理问题

    简介     在软件开发过程中,版本控制是一个广为人知的概念.因为一个项目可能会需要不同角色人员的参与,通过使用版本控制软件,可以使得项目中不同角色的人并行参与到项目当中.源代码控制使得代码可以存在多 ...