C#教学第24讲析构1(学习笔记)

第24讲 析构1

  视频讲师:陈广老师

    大家好,今天我们来讲一下C#中的析构(Destructor)。

    说到析构呢不得不说一下.NET Framework当中的垃圾回收机制,.NET 中的对象是创建在托管堆之上的,而我们以前使用的C++语言或者其他语言,他们的对象是创建在非托管之上的。在C++编程中,有很多的编程人员呢忘记了释放无用的内存,也有很多的编程人员试图访问已经被释放的内存,对于非托管编程来说,因为这2类应用程序的bug发生的时间和次序都难以预测,所以对应用程序来说它们所带来的危害远远超过了其他大多数的bug,这使得应用程序的结果变的无法预测。

    在.NET Framework中也就是托管堆编程中这种情况得到了极大的改善,回收内存的工作呢不需要编程人员去操心了。说到这我想到了一个很有意思的事情,上次我听wencast有个讲师在讲到析构的时候他用麦当劳做比喻,他说在国外的麦当劳,顾客吃完饭以后呢要自己把盘子端到服务台,而在中国的麦当劳呢我们吃完饭拍拍屁股就可以走了,清理盘子和垃圾的工作呢又麦当劳的工作人员代劳,根本不需要你去操心。简而言之就是中国的麦当劳非常先进,这个例子呢怎么听都有点粉刺的味道,但是不得不承认他比喻的非常形象。不管怎么说,.NET Framework的垃圾回收机制的确非常先进,它可以使得我们更加专注于应用程序的逻辑。

    说到垃圾回收机制呢首先要了解对象的生存期,也就是对象什么时候变成垃圾。下面我们来看一段小程序的代码:

    在程序首先运行的时候,首先创建了MainForm这个类,Application.Run(new MainForm());这段代码创建的;然后 构造函数中有一个InitializeComponent();函数,里面新建了一些控件对象。好,我们运行程序,这个时候在托管堆中已经有了MainForm这个类,然后还有他的成员tempint,还有字符串成员temps,当然在窗体中还包括了一个button和一个textbox。当我们单击这个按钮,这个时候一个整数类型k,并把它的初值设为10,当然这个是在堆栈中创建的,不用我们去操心,在堆栈中创建的对象呢它被弹出堆栈以后就没有用了。然后执行到下一步,它创建了一个字符串类型s,我们知道字符串类型呢它是一个引用类型的变量,它在托管堆中创建。整数类型是一个值类型对象,它是在堆栈中创建。继续下一步,for循环中继续创建了一个整数类型的i,当然这个是在堆栈中创建的,当执行完这个循环以后,这个i就自动消失了,因为我们再也不会使用它了。接着下一段代码调用了aMethod方法,传递了一个参数10,方法中又创建了一个整数类型的j,它只是做一个简单的返回,返回后j这个对象也已经没用了,会自动消失。

    接着执行完button的click事件后,我们的应用类型s就已经成为垃圾,因为它不会再被使用到,k,s,i三个变量他们都属于局部变量,他们都在方法内声明,或者在循环内声明,只要脱离了它们的作用域,就会自动变成垃圾,堆栈的垃圾只要被弹出堆栈它就自动消失了,而托管堆中的垃圾呢它还会存在于托管堆中,它要等待垃圾回收器在某个特定的时候呢对它进行回收,当然这个到后面会详细讲到。然后程序窗体的textbox中就会打印出一些内容,在这个时候还有一些对象还是存在于托管堆之上的。比如说MainForm,textbox,button,还有temps,它们还没有被释放,还没有成为垃圾。

    好,当我们关闭窗体后,包括MainForm,textbox,button,还有temps都会成为垃圾,等待被释放。好,大家可能要问了,托管堆中的垃圾什么时候会被回收呢?下面我们就来谈一下.NET Framework中的垃圾回收机制。

    在托管堆被初始化的时候,托管堆中不包括任何的对象,这是添加到托管堆中的对象被称为第0代对象。对象的创建如下图一样,是依次创建的,也就是说各个对象之间在内存之中是连续的。

 24

    就比如上图,我们在内存中创建了5个对象ABCDE,在CLR初始化的时候它会为第0代对象选择一个意向的容量,一般是256k,之所以让它的容量是256k是因为这样就可以把第0代对象完全装入cpu的L2k缓存中,也就是L2的Cache中。这样就使得内存的压缩速度非常的快。

    好,现在我们假设ABCDE它的大小就是256k,这个时候程序运行,C和E都成为了垃圾,我们用红色表示已经无用的对象,至于CLR如何去判定一个对象是否无用,是否已成为垃圾,这个呢有它特殊的算法,我们不必去关心。这个地方我们要注意,现在C和E虽然已成为垃圾,但是它依然存在于托管堆中,并没有被回收掉。如图:

24

这个时候我们准备创建一个新的对象F,但是我们看,第0代的空间已经被占满,已经不能在里面装下F对象了,这个时候呢就可以开始进入垃圾回收了。这个时候C和E就被释放掉了,内存就成这个样子。

24

我们看B和D之间还有D后就会有无用的内存空间,而这个时候就需要进行内存的压缩,把D移到前面去,从而保证了内存的连续性。经历了第一轮的垃圾回收之后呢,第0代的对象就变成了第一代的对象,这个时候就可以继续创建F的工作。我们看,刚才进行第一轮垃圾回收还保留着ABD三个对象现在已经变成了第一代,而F呢则在第0代创建。我们看一下图:

24

接下来可以继续创建对象,在第0代上创建了GHIJK5个对象,这个时候第0代的空间已经被占满。如图:

24

好,程序继续运行,接下来BHJ相继成为了垃圾对象。如图:

24

假设,现在要创建一个L对象,而第0代的空间已经被占满,所以要进行第二次垃圾回收。这个时候垃圾收集器必须要决定先检查哪一代对象,我们刚才已经讲过,第0代的对象它的大小一般是256k,第一代它也有一个大小,第一代的运算容量是2M,当垃圾收集开始执行时,垃圾回收器会查看第一代对象占用了多少内存。当然,在本例中第一代在内存所占用的内存远远少于2M,所以这一次垃圾收集呢仅仅检查第0代的对象。J和H被释放掉了,如图:

24 

这个地方要注意,B并不被释放,紧接着压缩内存,如图:

24

所有的对象都变成了第一代,很明显在第1代对象未满之前,忽略对它的检查,可以改善垃圾收集器的性能。然后在第0代的空间创建L。之后再继续创建对象M,N,O。在第0代的空间上创建了L,M,N,O四个对象,如图:

24

随着程序的运行,G,L,M也变成了垃圾对象。

24

这时候分配了对象P。假设分配对象P又导致了第0代对象超过了它的预算容量,这个时候呢第三次的垃圾收集就被启动了,这个时候第一代的使用空间仍然小于2M,所以呢,和刚才一样,垃圾收集器仍只收集第0代的空间。L,M被释放了,紧接着压缩内存。第一代的空间逐渐增大,然后分配P。

24

继续创建对象Q,R,S。随着程序的运行,有了更多的对象成为了垃圾,如图:

24

这时候要创建T对象,而第0代的空间已经无法容纳下T对象,这个时候就要进行第4次的垃圾回收,我们假设经过第三次的垃圾回收,第1代的空间已经超过了2M。而当时垃圾收集刚刚完成,所以并不会马上对第1代的空间进行回收,而在第四次的垃圾回收之前呢,它们发现第1代的空间已经超过了2M,所以呢这次决定对第1代和第0代同时进行垃圾收集。

首先它会收集第1代的垃圾,接下来进行内存的压缩。这个地方要注意,第1代的对象经过垃圾器的回收以后,就变成了第2代。第2代的空间大约为10M,非常大,这个时候第1代收集完了,如图:

24

然后开始收集第0代的空间,P和R成为了垃圾,被释放掉,这时候和刚才一样Q和S升级了第1代,如图:

24

 我们看,经过4次垃圾回收之后,第1代对象中存活者被提升为第2代对象,而第0代对象中存活者被提升为第1代对象,第0代空缺。随着程序的运行,F,N或者其他对象都有可能变成垃圾,但是只要第2代的空间10M没有充满,它们就不会被垃圾回收。也就是说,第2代被垃圾收集的频率是最小的,而第1代次之,第0代的垃圾收集频率是最高的。

我们很容易就可以想到,运算容量越大垃圾收集的频率也就越低,CLR的垃圾收集器是一个自调节的垃圾收集器。这意味着垃圾收集器在执行垃圾收集的过程中学习应用程序的行为,它可以根据应用程序的实际情况,将第0代的运算容量从256K减到128K,从而提高程序的性能。

刚才我们所讲的这一切都是有CLR自动完成的,不需要我们程序员写任何的代码。总而言之,.NET Framework的垃圾回收机制是非常先进的。了解这些机制,对我们将来写出高性能的应用程序有着非常重要的意义。好今天的课就讲到这里。

 由快乐乔巴听课摘写笔记

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值