C#基础之垃圾回收
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#基础之垃圾回收的更多相关文章
- JVM基础(5)-垃圾回收机制
一.对象引用的类型 Java 中的垃圾回收一般是在 Java 堆中进行,因为堆中几乎存放了 Java 中所有的对象实例.谈到 Java 堆中的垃圾回收,自然要谈到引用.在 JDK1.2 之前,Java ...
- jvm基础知识—垃圾回收机制
1.首先类的实例化.static.父类构造函数执行顺序 我们来看下面的程序代码: public class A { int a1 = 8; { int a3 = 9; System.out.print ...
- java基础之 垃圾回收机制
1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...
- Java基础之垃圾回收
/** * 对象在没有任何引用可以到达时,生命周期结束,成为垃圾. * 所有对象在被回收之前都会自动调用finalize()方法. * ******************************** ...
- JS基础_垃圾回收(GC)
垃圾回收(GC) 程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我门需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾 当一个对象没有任何的变量或属性对它进行引用 ...
- Java基础教程——垃圾回收机制
垃圾回收机制 Garbage Collection,GC 垃圾回收是Java的重要功能之一. |--堆内存:垃圾回收机制只回收堆内存中对象,不回收数据库连接.IO等物理资源. |--失去使用价值,即为 ...
- Java基础:JVM垃圾回收算法
众所周知,Java的垃圾回收是不需要程序员去手动操控的,而是由JVM去完成.本文介绍JVM进行垃圾回收的各种算法. 1. 如何确定某个对象是垃圾 1.1. 引用计数法 1.2. 可达性分析 2. 典型 ...
- 【Java_基础】JVM内存模型与垃圾回收机制
1. JVM内存模型 Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间. JVM内存模型如下图所示 1.1 程序计数器 程序计数器( ...
- 【转载】Java垃圾回收机制
原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...
随机推荐
- MYSQL性能调优: 对聚簇索引和非聚簇索引的认识
聚簇索引是对磁盘上实际数据重新组织以按指定的一个或多个列的值排序的算法.特点是存储数据的顺序和索引顺序一致.一般情况下主键会默认创建聚簇索引,且一张表只允许存在一个聚簇索引. 在<数据库原理&g ...
- PS网页设计教程XXVI——如何在PS中创建一个专业的网页布局
作为编码者,美工基础是偏弱的.我们可以参考一些成熟的网页PS教程,提高自身的设计能力.套用一句话,“熟读唐诗三百首,不会作诗也会吟”. 本系列的教程来源于网上的PS教程,都是国外的,全英文的.本人尝试 ...
- 在IT的路上,我在成长
在IT的路上,我在成长.很荣幸地加入了博客园这个大家庭. 岁月的航船在不断航行,在成长的脚印我要深深留下,回首已往经历,发现自己成长的路上,将来也会有很多美好的回忆,以及丰硕的果实.
- 将text 文件转为List
Integer 类型 ArrayList<Integer> Mlist = new ArrayList<Integer>(); Scanner scM = new Scanne ...
- [部署]CentOS配置IP地址
环境 虚拟机:VMWare10.0.1 build-1379776 操作系统:CentOS7 64位 简介 CentOS7最小化安装(Minimal)时,是不带ifconfig指令的,该指令在net- ...
- Android分步注册,Activity由B返回A修改再前往B,B中已填项不变
某日突然想到标题问题,一般来说返回上一个Activity,当前Activity应该自动销毁.要想保留值,便想到用bundle传递的方式 最后功能是实现了,但感觉方法很笨. 主要代码如下: packag ...
- hadoop core-site.xml
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text ...
- mvc area区域和异步表单,bootstrap简单实例
码农最怕眼高手低 今天来练习mvc Area技术和bootstrap以及异步表单的C#代码实现. 1.area区域架构对于建立复杂业务逻辑很有帮助,由 AreaRegistration.Regist ...
- UVA-10828 (概率期望+高斯消元)
题意: 给个有向图,每个节点等概率转移到它的后继节点,现在问一些节点的期望访问次数; 思路: 对于一个点v,Ev=Ea/d[a]+Eb/d[b]+Ec/d[c];a,b,c是v的前驱节点; 然后按这个 ...
- uGUI练习(三) KeyBoard Navigation
练习目标 练习通过键盘在按钮或其它Selectable类型组件上导航 步骤 创建一排的Button,及一个右边的Button 2.查看Button的属性里有一栏下拉列表Navigation,默认选择的 ...