1.托管资源的回收

  我们都知道C#托管资源的回收由GC全权负责控制,可是什么时候GC会回收垃圾呢?一般出现以下情况会回收垃圾:手动调用GC.Collect()强制回收;第0代对象内存已满;应用程序域被卸载时,CLR会回收所有资源;windows报告内存不足。其中作为开发人员,我们应该尽量少使用Collect方法,除非已经很明确的知道内存中有很多大对象需要被回收,否则随意使用Collect方法会导致扰乱GC正常的工作方式,从而扰乱了应用程序的内存使用。现在来看一个完整的托管垃圾回收过程,如下图所示。从这个过程可以看到分代回收的优点:经过几次垃圾回收后全局对象等生存时间较长的对象将被放到第二代,由于第0代空间很小因此可以快速遍历寻找未被引用的对象,而正好第0代总是有生存时间较短的新对象加入,最终的效果就是提高了性能。在图中还提到了根的概念,每个应用程序都有由JIT编译器和CLR运行时维护的一组指向托管堆中内存的根指针,主要包括全局变量、静态变量、局部变量、寄存器指针。当遍历代中堆的对象时,一个根将作为一个入口点,我们说的对象被引用指的是在根上有指向对象的指针。如下图a指向objA,objA、objB、objC形成一个环,此时如果a离开了作用域而被释放,objB和objC也会被标记为不可达,GC只会标记一次之后不会再标记。

  当GC把不可达的对象标记为不可达时,并不会就开始释放资源,而是会检查Finalization队列中是否有指针指向不可达对象,如果有这样的对象则会将Finilazation队列中的指针移到Freachable队列中。Freachable队列中一旦有指针则会触发指向的对象去执行Finalize方法,之后再从队列中删除这个指针,被指向的对象就可以由GC回收了。这样设计也是为了彻底的释放资源,当new一个含有Finalize方法的对象时,就会在Finalization队列中添加指向这个对象的指针。一般我们使用Finalize方法是为了释放非托管资源,所以为了彻底释放资源我们要保证非托管资源也被正确释放了。

垃圾回收就是不断地循环上述的步骤,当第二代内存也满了则会对第二代内存来一次垃圾回收,不过这种情况发生的概率很低。另外在程序运行过程中这3代的内存大小并不是不变的,而是会根据实际情况动态改变的。现在假设执行完了一次垃圾回收,将第0代和第1代的不可达对象清理了。可以想象在堆中存在着许多碎片,连续的代内存变得不再连续。因此GC会线程挂起接着来一次内存压缩,在本例中是将第0代和第1代的剩余对象转移到第2代中,从这个过程中可以发现并不是简单的复制对象就可以了,因为根中的指针所指向的对象已被转移。所以此时GC需要修改应用程序的根指针和发生对象引用的指针,让这些指针指向新的对象内存地址。如果这里有一个对象是被非托管资源所指向的,由于GC无法去修改非托管资源的指针,因此这个对象将不会被转移。

2.非托管资源的回收

  在C#中当我们使用非托管资源比如文件操作、数据库链接、套接字时就使用了非托管资源,比如文件操作程序中我们会在操作非托管资源时使用using包起来或者最后调用Close()方法。使用Reflector工具可以看到加上using语句块其实就是在程序中最后添加了Close方法,Close方法则是调用了Dispose方法,之后又调用了SuppressFinalize方法让Finalize方法禁止调用。从Close方法的内部实现可以看出在C#中释放非托管资源的工作是交给Finalize与Dispose这2个方法了。Finalize方法是有我们程序员自己定义的一个方法来释放内存,调用时间是上面提到的GC垃圾回收前。Dispose方法是实现了IDispose接口中的Dispose方法,开发者直接调用来释放非托管资源。

  在VS中创建一个FileStream对象fs,会发现使用点是无法点出Finalize方法的,F12进去也没有看到这个方法。这是因为.NET对其做了规定,开发人员只能通过析构函数来实现,不能显示的进行调用。如下面代码所示,如果我在析构函数中不加上sw.Close()的话te.txt打开是什么都看不到的,因为这时非托管资源没有释放。也就是说这里析构函数没起作用,它只是一个声明告诉CLR这个对象的指针需要添加到Finalization中,因此我们需要在析构函数中手动添加Close方法去释放资源。再来看看IL代码,可以看到析构函数在IL中就是一个Finalize方法,方法里面又调用了父类的Finalize方法。从这可以看出Finalize方法的一个特点,子类Finalize方法中会调用父类的Finalize方法,这样递归调用可保证所有父类直到object的资源都被清理掉,不过这对性能也是一个很大的损失啊。现在可以总结Finalize的工作原理了,首先Object类有一个受保护的实现了的虚方法,.NET要求每一个释放非托管资源的类通过析构函数的方式重写这个方法,当然也可以不重写,如果没有重写则Finalization队列中不会添加这个这个对象的指针。如果添加了析构函数,则需要在析构函数中编写释放资源的代码,说到底Finalize方法需要我们程序员手动的释放非托管资源。而且它被调用的时机还不知道,只知道是在一个对象变为不可达后才会被调用,这样的话可能在下一个GC回收周期非托管资源才被释放或者代数的增加。另外Finalization和Freachable2个队列的维护以及GC开新线程去执行Finalize方法(包括父类的)都将带来性能的损耗。

    public class MyClass
{
StreamWriter sw;
~MyClass()
{
//进行资源的清理
sw.Close();
}
public void Func()
{
FileStream fs = new FileStream("D:\\te.txt", FileMode.OpenOrCreate);
sw = new StreamWriter(fs);
string str="哈哈";
sw.Write(str);
}
}

从上面可以看出对于非托管资源的释放,Dispose方法是首选,只需我们手动的编写一条代码即可释放,控制权在程序员手中并且性能比Finalize要好。关于Dispose的工作模式可查看我的另一篇随笔cnblogs.com/fangyz/p/5293888.html,一般操作非托管资源的类都重写了Dispose方法,比如可以在VS中看到FileStream的Dispose方法。如果我们要在自定义类中重写Dispose方法,最后要加上base.Dispose(),这样可保证继承链上的父类资源也释放了资源。

声明:本文原创发表于博客园,作者为方小白,如有错误欢迎指出 。本文未经作者许可不许转载,否则视为侵权。

C#基础之垃圾回收的更多相关文章

  1. JVM基础(5)-垃圾回收机制

    一.对象引用的类型 Java 中的垃圾回收一般是在 Java 堆中进行,因为堆中几乎存放了 Java 中所有的对象实例.谈到 Java 堆中的垃圾回收,自然要谈到引用.在 JDK1.2 之前,Java ...

  2. jvm基础知识—垃圾回收机制

    1.首先类的实例化.static.父类构造函数执行顺序 我们来看下面的程序代码: public class A { int a1 = 8; { int a3 = 9; System.out.print ...

  3. java基础之 垃圾回收机制

    1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...

  4. Java基础之垃圾回收

    /** * 对象在没有任何引用可以到达时,生命周期结束,成为垃圾. * 所有对象在被回收之前都会自动调用finalize()方法. * ******************************** ...

  5. JS基础_垃圾回收(GC)

    垃圾回收(GC) 程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我门需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾 当一个对象没有任何的变量或属性对它进行引用 ...

  6. Java基础教程——垃圾回收机制

    垃圾回收机制 Garbage Collection,GC 垃圾回收是Java的重要功能之一. |--堆内存:垃圾回收机制只回收堆内存中对象,不回收数据库连接.IO等物理资源. |--失去使用价值,即为 ...

  7. Java基础:JVM垃圾回收算法

    众所周知,Java的垃圾回收是不需要程序员去手动操控的,而是由JVM去完成.本文介绍JVM进行垃圾回收的各种算法. 1. 如何确定某个对象是垃圾 1.1. 引用计数法 1.2. 可达性分析 2. 典型 ...

  8. 【Java_基础】JVM内存模型与垃圾回收机制

    1. JVM内存模型 Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间. JVM内存模型如下图所示 1.1 程序计数器 程序计数器( ...

  9. 【转载】Java垃圾回收机制

    原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...

随机推荐

  1. CSRF 攻击原理和防御方法

    1. CSRF攻击原理 CSRF(Cross site request forgery),即跨站请求伪造.我们知道XSS是跨站脚本攻击,就是在用户的浏览器中执行攻击者的脚本,来获得其cookie等信息 ...

  2. JS高级程序设计2nd部分知识要点1

    保存浮点数值需要的内存空间是保存整数值的两倍,因此ECMAScript会不失时机的将浮点数值转换为整数值 浮点数值的最高精度是17位小数 parseInt 字符串转换为数值,可传基数(8,16) pa ...

  3. MAVEN整理(乘国庆还有时间,停下来整理一下)

    昨天写下了这篇博客(http://www.cnblogs.com/hzmark/p/131003Release.html),今天有时间,就这里一下第一篇内容. 换工作公司开发平台: WINDOWS+V ...

  4. c#MD5珍藏

    c#MD5珍藏 using System; using System.Collections.Generic; using System.Text; namespace MD5 { public cl ...

  5. C#连接MySql数据库的方法

    1.要连接MySql数据库必须首先下载MySql的连接.net的文件, 文件下载地址为http://download.csdn.net/detail/xiaoliu123586/91455792.解压 ...

  6. Mac OS X 中vim开启配色和git补全

    问题起源:mac 下自带的git 无法补全命令,配色方案不完善 Mac OS Yosemite 10.10.3 git version 2.3.2 (Apple Git-55) 处理结果:git命令可 ...

  7. 我的vim配置文件.vimrc

    我的vim配置文件.vimrc map <silent> <F10> :TlistToggle<cr>map <silent> <F8> : ...

  8. uva 839 not so mobile——yhx

    Not so Mobile  Before being an ubiquous communications gadget, a mobile was just a structure made of ...

  9. 《EXO指数型组织》阅读脑图

    书籍:<EXO指数型组织:打造独角兽公司的11个最强属性> 预览链接:https://www.processon.com/view/link/57ce8279e4b08cbf6ca8f85 ...

  10. UESTC 919 SOUND OF DESTINY --二分图最大匹配+匈牙利算法

    二分图最大匹配的匈牙利算法模板题. 由题目易知,需求二分图的最大匹配数,采取匈牙利算法,并采用邻接表来存储边,用邻接矩阵会超时,因为邻接表复杂度O(nm),而邻接矩阵最坏情况下复杂度可达O(n^3). ...