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联 ...
随机推荐
- Android开发艺术探索学习笔记(十一)
第十一章 Android的线程和线程池 从用途上来说,线程分为子线程和主线程,主线程主要处理和界面相关的事情,而子线程往往用于执行耗时的操作.AsyncTask,IntentService,Hand ...
- (ios)ARC常用设置,部分文件编译设置ARC
1设置 整个项目是否支持ARC 选择No 不支持 YES 支持 2 设置部分文件是否支持ARC. 支持ARC的Flags -fobjc-arc 不支持的Flags -fno-objc-arc
- 设计模式C#实现(九)——工厂方法模式和简单工厂
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method使一个类的实例化延迟到其子类. 构成: 1.Product工厂方法创建的对象的接口 2.Concrete ...
- Error && MFC
Error:MSB6006 "rc.exe" exited with code 2. 目录含有中文 Error:no instance of overloaded function ...
- Spring @Transactional ——事务回滚
工作原理运行配置@Transactional注解的测试类的时候,具体会发生如下步骤1)事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与Dat ...
- html弹窗半透明
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...
- nginx看端口使用情况
[root@iZ94j7ahvuvZ sbin]# netstat -apn Active Internet connections (servers and established) Proto R ...
- 杂谈SharpDx中的WIC组件——我们需要WIC的图片编码功能么?
在前文 SharpDX之Direct2D教程II——加载位图文件和保存位图文件 中,发现在VB2010中不能很好的运用SharpDx中的WIC组件进行图片的编码工作.可能是我的设置问题,也可能是Sha ...
- jemter的使用(三)
前面的文章已经把接口请求.响应等前序工作做好,那么如何施加压力呢,看下面 1.点击线程组,设置线程属性,其中:线程数即并发用户数,ramp-up period是多长时间初始化上面的并发用户数,循环次数 ...
- 原创翻译-值得关注的10个python语言博客
原文链接 原文链接的网页感觉网络不是很好,不容易上.我在这里就给大家做个翻译吧. 大家好,还记得我当时学习python的时候,我一直努力地寻找关于python的博客,但我发现它们的数量很少.这也是我建 ...