NET垃圾回收机制.doc

上传人:cl****1 文档编号:559865916 上传时间:2023-12-21 格式:DOC 页数:19 大小:68.01KB
返回 下载 相关 举报
NET垃圾回收机制.doc_第1页
第1页 / 共19页
NET垃圾回收机制.doc_第2页
第2页 / 共19页
NET垃圾回收机制.doc_第3页
第3页 / 共19页
NET垃圾回收机制.doc_第4页
第4页 / 共19页
NET垃圾回收机制.doc_第5页
第5页 / 共19页
点击查看更多>>
资源描述

《NET垃圾回收机制.doc》由会员分享,可在线阅读,更多相关《NET垃圾回收机制.doc(19页珍藏版)》请在金锄头文库上搜索。

1、在.NET Framework中,内存中的资源(即所有二进制信息的集合)分为托管资源和非托管资源.托管资源必须接受.NET Framework的CLR(通用语言运行时)的管理(诸如内存类型安全性检查),而非托管资源则不必接受.NET Framework的CLR管理. (了解更多区别请参阅.NET Framework或C#的高级编程资料) 托管资源在.NET Framework中又分别存放在两种地方: 堆栈和托管堆(以下简称堆);规则是,所有的值类型(包括引用和对象实例)和引用类型的引用都存放在堆栈中,而所有引用所代表的对象实例都保存在堆中。 在.NET中,释放托管资源是可以自动通过垃圾回收器完

2、成的(注意,垃圾回收机制是.NET Framework的特性,而不是C#的),但具体来说,仍有些需要注意的地方:1.值类型和引用类型的引用其实是不需要什么垃圾回收器来释放内存的,因为当它们出了作用域后会自动释放所占内存(因为它们都保存在堆栈中,学过数据结构可知这是一种先进后出的结构); 2.只有引用类型的引用所指向的对象实例才保存在堆中,而堆因为是一个自由存储空间,所以它并没有像堆栈那样有生存期(堆栈的元素弹出后就代 表生存期结束,也就代表释放了内存),并且非常要注意的是,垃圾回收器只对这块区域起作用; 3.垃圾回收器也许并不像许多人想象的一样会立即执行(当堆中的资源需要释放时),而是在引用类

3、型的引用被删除和它在堆中的对象实例被删除中间有 个间隔,为什么呢? 因为垃圾回收器的调用是比较消耗系统资源的,因此不可能经常被调用;(当然,用户代码可以用方法System.GC.Collect()来强制执行垃圾回收器) 4有析构函数的对象需要垃圾收集器两次处理才能删除:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象; 5由于垃圾收集器的工作方式,无法确定C#对象的析构函数何时执行; 6可实现IDisposable接口的Dispose()来显示释放由对象使用的所有未托管资源;7垃圾收集器在释放了它能释放的所有对象后,就会压缩其他对象,把他们都移动回托管堆的端部,再次形成一个连续的块

4、。 导言 垃圾回收(Garbage Collection)在.net中是一个很重要的机制。本文将要谈到CLR4.0对垃圾回收做了哪些改进。为了更好地理解这些改进, 本文也要介绍垃圾回收的历史。这样我们对整个垃圾回收有一个大的印象。这个大印象对于我们掌握.net架构是有帮助的。 关于垃圾回收 在C+时代,我们需要自己来管理申请内存和释放内存. 于是有了new, delete关键字. 还有的一些内存申请和释放函数(malloc/free)。C+程序必须很好地管理自己的内存,不然就会造成内存泄漏(Memory leak)。在.net时代, 微软为开发人员提供了一个强有力的机制-垃圾回收,垃圾回收机制

5、是CLR的一部分, 我们不用操心内存何时释放,我们可以花更多精力关注应用程序的业务逻辑。CLR里面的垃圾回收机制用一定的算法判断某些内存程序不再使用,回收这些内存并交给我们的程序再使用. 垃圾回收的功能 用来管理托管资源和非托管资源所占用的内存分配和释放。 寻找不再使用的对象,释放其占用的内存, 以及释放非托管资源所占用的内存。 垃圾回收器释放内存之后, 出现了内存碎片, 垃圾回收器移动一些对象,以得到整块的内存,同时所有的对象引用都将被调整为指向对象新的存储位置。下面我们来看看CLR是如何管理托管资源的。托管堆和托管栈 .net CLR在运行我们的程序时,在内存中开辟了两块地方作不同的用处-

6、托管栈和托管堆. 托管栈用来存放局部变量, 跟踪程序调用与返回。托管堆用来存放引用类型。引用类型总是存放于托管堆。值类型通常是放在托管栈上面的. 如果一个值类型是一个引用类型的一部分,则此值类型随该引用类型存放于托管堆中。哪些东西是值类型? 就是定义于System.ValueType之下的这些类型: bool byte char decimal double enum float int long sbyte short struct uint ulong ushort 什么是引用类型呢? 只要用class, interface, delegate, object, string声明的类型,

7、就是引用类型。 我们定义一个局部变量, 其类型是引用类型。当我们给它赋一个值,如下例:private void MyMethod() MyType myType = new MyType(); myType.DoSomeThing(); 在此例中, myType 是局部变量, new实例化出来的对象存储于托管堆, 而myType变量(引用部分)存储于托管栈。在托管栈的myType变量存储了一个指向托管堆上new实例化出来对象的引用。CLR运行此方法时, 将托管栈指针移动, 为局部变量myType分配空间, 当执行new时, CLR先查看托管堆是否有足够空间, 足够的话就只是简单地移动下托管堆的

8、指针,来为MyType对象分配空间,如果托管堆没有足够空间,会引起垃圾收集器工作。CLR在分配空间之前,知道所有类型的元数据,所以能知道每个类型的大小,即占用空间的大小。 当CLR完成MyMethod方法的执行时, 托管栈上的myType局部变量被立即删除, 但是托管堆上的MyType对象却不一定马上删除。这取决于垃圾收集器的触发条件。后面要介绍此触发条件。 注意:这里强调了,当对象的作用域结束后,他的引用由于在堆栈中,所以立刻被删除了,但是对象实例本身在托管堆中,因此不会立刻删除,而是等待的垃圾回收机制的运行。 上面我们了解了CLR如何管理托管资源。下面我们来看垃圾收集器如何寻找不再使用的托

9、管对象,并释放其占用的内存。垃圾收集器如何寻找不再使用的托管对象,并释放其占用的内存 前面我们了解了CLR如何管理托管栈上的对象。按照先进后出原则即可比较容易地管理托管栈的内存。托管堆的管理比托管栈的管理复杂多了。下面所谈都是针对托管堆的管理。根 垃圾收集器寻找不再使用的托管对象时, 其判断依据是当一个对象不再有引用指向它, 就说明此对象是可以释放了。一些复杂的情况下可以出现一个对象指向第二个对象,第二个对象指向第三个对象,就象一个链表。那么,垃圾收集器从哪里开始查找不再使用的托管对象呢? 以刚才所说的链表为例,显然是应该从链表的开头开始查找。那么,在链表开头的是些什么东东呢? 是局部变量,

10、全局变量, 静态变量, 指向托管堆的CPU寄存器。在CLR中,它们被称之为根。 有了开始点,垃圾收集器接下来怎么做呢? 创建一个图, 一个描述对象间引用关系的图. 垃圾收集器首先假定所有在托管堆里面的对象都是不可到达的(或者说没有被引用的,不再需要的), 然后从根上的那些变量开始, 针对每一个根上的变量,找出其引用的托管堆上的对象,将找到的对象加入这个图, 然后再沿着这个对象往下找,看看它有没有引用另外一个对象,有的话,继续将找到的对象加入图中,如果没有的话,就说明这条链已经找到尾部了。垃圾收集器就去从根上的另外一个变量开始找,直到根上的所有变量都找过了, 然后垃圾收集器才停止查找。值得一提的

11、是,在查找过程中, 垃圾收集器有些小的优化,如: 由于对象间的引用关系可能是比较复杂的, 所以有可能找到一个对象, 而此对象已经加入图了, 那么垃圾收集器就不再在此条链上继续查找, 转去其他的链上继续找。这样对垃圾收集器的性能有所改善。 垃圾收集器建好这个图之后, 剩下那些没有在这个图中的对象就是不再需要的. 垃圾收集器就可以回收它们占用的空间。 内存释放和压缩 创建对象引用图之后,垃圾回收器将那些没有在这个图中的对象(即不再需要的对象)释放。释放内存之后, 出现了内存碎片, 垃圾回收器扫描托管堆,找到连续的内存块,然后移动未回收的对象到更低的地址, 以得到整块的内存,同时所有的对象引用都将被

12、调整为指向对象新的存储位置。这就象一个夯实的动作。也就是说,一个对象即使没有被清除,由于内存压缩,导致他的引用位置发生变化。 下面要说到的是代的概念。代概念的引入是为了提高垃圾收集器的整体性能。代 请想一想如果垃圾收集器每次总是扫描所有托管堆中的对象,对性能会有什么影响。会不会很慢?是的。微软因此引入了代的概念。 为什么代的概念可以提高垃圾收集器的性能?因为微软是基于对大量编程实践的科学估计,做了一些假定而这些假定符合绝大多数的编程实践:越新的对象,其生命周期越短。 越老的对象,其生命周越长。 新对象之间通常有强的关系并被同时访问。 压缩一部分堆比压缩整个堆快。 有了代的概念,垃圾回收活动就可

13、以大部分局限于一个较小的区域来进行。这样就对垃圾回收的性能有所提高。让我们来看垃圾收集器具体是怎么实现代的: 第0代:新建对象和从未经过垃圾回收对象的集合 第1代:在第0代收集活动中未回收的对象集合 第2代:在第1和第2代中未回收的对象集合, 即垃圾收集器最高只支持到第2代, 如果某个对象在第2代的回收活动中留下来,它仍呆在第2代的内存中。 当程序刚开始运行,垃圾收集器分配为每一代分配了一定的内存,这些内存的初始大小由.net framework的策略决定。垃圾收集器记录了这三代的内存起始地址和大小。这三代的内存是连接在一起的。第2代的内存在第1代内存之下,第1代内存在第0代内存之下。应用程序

14、分配新的托管对象总是从第0代中分配。如果第0代中内存足够,CLR就很简单快速地移动一下指针,完成内存的分配。这是很快速的。当第0代内存不足以容纳新的对象时,就触发垃圾收集器工作,来回收第0代中不再需要的对象,当回收完毕,垃圾收集器就夯实第0代中没有回收的对象至低的地址,同时移动指针至空闲空间的开始地址(同时按照移动后的地址去更新那些相关引用),此时第0代就空了,因为那些在第0代中没有回收的对象都移到了第1代。 当只对第0代进行收集时,所发生的就是部分收集。这与之前所说的全部收集有所区别(因为代的引入)。对第0代收集时,同样是从根开始找那些正引用的对象,但接下来的步骤有所不同。当垃圾收集器找到一

15、个指向第1代或者第2代地址的根,垃圾收集器就忽略此根,继续找其他根,如果找到一个指向第0代对象的根,就将此对象加入图。这样就可以只处理第0代内存中的垃圾。这样做有个先决条件,就是应用程序此前没有去写第1代和第2代的内存,没有让第1代或者第2代中某个对象指向第0代的内存。但是实际中应用程序是有可能写第1代或者第2代的内存的。针对这种情况,CLR有专门的数据结构(Card table)来标志应用程序是否曾经写第1代或者第2代的内存。如果在此次对第0代进行收集之前,应用程序写过第1代或者第2代的内存,那些被Card Table登记的对象(在第1代或者第2代)将也要在此次对第0代收集时作为根。这样,才可以正确地对第0代进行收集。 以上说到了第0代收集发生的一个条件,即第0代没有足够内存去容纳新对象。执行GC.Collect()也会触发对第0代的收集。另外,垃圾收集器还为每一代都维护着一个监视阀值。第0代内存达到这个第0代的阀值时也会触发对第0代的收集。对第1代的收集发生在执行GC.Collect(1)或者第1代内存达到第1代的阀值时。第2代也有类似的触发条件。

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 社会民生

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号