C#垃圾回收和资源管理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_44800780/article/details/102721233

计算机内存有限,当变量或对象不在需要内存的时候,必须回收内存。值类型离开作用域就会被销毁,引用类型呢?

对象生存期

Monkey monkey = new Monkey();

上面的代码中new表面上是单步操作,但实际分两步走:
1.new从堆中分配原始内存,这个阶段无法干预
2.new操作符将原始内存转换成对象,这时必须初始化对象。这个阶段可用构造器控制.
monkey变量离开作用域时,它引用的Monkey对象就没人引用了,所以对象可被销毁,占用的内存可被回收.
和对象创建相似,对象销毁也分两步:
1.CLR执行清理工作,可以写一个析构器来加以控制。
2.CLR将对象占用的内存归还给堆,解除对象内存分配,这个阶段没有控制权.

销毁对象并将内存归还给堆的过程称为垃圾回收.

编写析构器

使用析构器,可在对象被垃圾回收时执行必要的清理.CLR能自动清理对象使用的任何托管资源,所以许多时候都不需要自己写析构器.但如果托管资源很大,就可以考虑将堆该资源的所有引用设为null,使资源能被理解清理。
另外,如果对象引用了非托管资源,析构器就更有用了.

析构器的语法是先写一个~符号,在添加类名:
在这里插入图片描述
注意:
1.析构器只适合引用类型,值类型不能声明析构器
2.不能为析构器指定访问修饰符,因为析构器总是由垃圾回收器帮你调用,你不能自己调用.
3.析构器不能获取任何参数.因为你自己调用不了

编译器内部自动将析构器转换成对Object.Finalize方法的一个重写方法的调用.

    class Monkey
    {
        ~Monkey()
        {
			...
        }
    }

将会转换成以下形式:

    class Monkey : Mammal
    {
        Protected override void Finalize()
        {
			try{//....};
			finally{base.Finalize();}
        }
    }

编译器生成的Finalize方法析构器的主体包含到try块中,后跟finally块来调用基类的Finalize方法,这样即使你的析构器代码发生了异常,能确保析构器总是调用其基类析构器.

注意:只有编译器才能进行这个转换。你不能自己重写Finalize,也不能自己调用Finalize.


为什么使用垃圾回收器?

永远不能用C#代码自己销毁对象。相反CLR在它认为合适的时候帮你做这件事情.
一个对象能有多少对它的引用?答案是没有限制.
CLR必须跟踪所有引用,比如:
在这里插入图片描述
如果变量monkey1不存在了,monkey2可能还存在.
此时Monkey对象使用的资源还不能回收。只有在对一个对象的所有引用都消失之后,才可以销毁该对象.
可以看出,对象生存期是一个相当复杂的事情,这正是C#设计者决定禁止由你销毁对象的原因.
如果自己销毁对象,迟早会遇到以下情况之一:
1.忘记销毁对象,最终的结果是,内存很快被消耗完.
2.试图销毁活动对象.
3.试图多次销毁同一个对象.

对于C#这种问题显然不能接受,从而使用垃圾回收器负责销毁对象.垃圾回收器能做出以下几点担保:
1.每个对象都会被销毁,它的析构器会运行.
2.每个对象都只被销毁一次.
3.每个对象在它不可达时,才会被销毁.

垃圾回收器的特定是:程序员不知道对象的销毁顺序,析构器只有在对象被垃圾回收时才运行,析构器肯定会运行,只是不保证在什么时候运行。所以不要对析构器运行顺序或时间做任何假设.

扫描二维码关注公众号,回复: 7601739 查看本文章

垃圾回收器的工作原理

垃圾回收器在它自己的线程中运行,而且只在特定的时候才会执行,它运行时,应用程序中运行的其他线程将暂停。这是由于垃圾回收器可能需要移动对象并更新对象引用。

垃圾回收器是非常复杂的,它采取的大体步骤如下:

1.构造所有可达对象的映射。它会反复跟随对象中的引用字段。垃圾回收器会非常小心地构造映射,确保循环引用不会造成无限递归.

2.检测是否有任何不可达对象包含一个需要运行的析构器(运行析构器的过程称为终结).需要终结的任何不可达对象都放到一个称为freachable的特殊队列中.

3.回收剩下的不可达对象(即不需要中介的对象).它会在堆中向下移动可达的对象,对堆进行"碎片整理",释放位于堆顶部的内存.一个可达对象被移动之后,会更新堆该对象的所有引用.

4.允许其他线程恢复执行.

5.在一个独立的线程,对需要终结的不可达对象执行终结操作.

慎用析构器

写包含析构器的类,会使用代码和垃圾回收变得复杂。还会影响到程序的运行速度。
此外,如果在析构器中调用其他对象,那些对象的析构器可能已被垃圾回收器调用.

资源管理

有时候在析构器中释放资源并不明智,有些资源过于宝贵,用完后应该马上就释放,而不是等垃圾回收器在某个不确定的时刻释放。
这时唯一的选择就是亲自释放资源,可以通过自己写的资源清理方法来实现。

资源清理方法

实现了资源清理方法的一个例子是System.IO命名空间的TextReader类,该类提供了从顺序输入流中读取字符的机制.
TextReader包含虚方法Close,负责关闭流,
比如

            TextReader reader = new StreamReader(filename);
            string line;
            while((line = reader.ReadLine())!= null)
            {
                Console.WriteLine(line);
            }
            reader.Close();

但是这个例子存在一个问题,它不是异常安全的。如果对ReadLine的调用抛出异常,对Close的调用就不会发生.

异常安全的资源清理

为了确保资源清理方法总是得到调用,一个办法是在finally块中调用该方法:

            TextReader reader = new StreamReader(filename);
            try
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }
            finally
            {
                reader.Close();
            }

这个方法比较麻烦,更简洁的放是使用using语句

using语句和IDisposable接口

using语句提供一个机制来控制资源的生存期.可创建一个对象,该对象在using语句块结束时销毁.
using语句的语法如下:

using (type variable = initialization)
{
	statementBlock
}

比如:

            using (TextReader reader = new StreamReader(filename))
            {
                string line;
                while((line = reader.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }

using语句声明的变量的类型必须实现IDisposable接口.
在这里插入图片描述
Dispose方法的作用是清理对象使用的任何资源.

猜你喜欢

转载自blog.csdn.net/qq_44800780/article/details/102721233