深入理解NET内存回收机制
- 格式:doc
- 大小:39.50 KB
- 文档页数:4
2021年⾮常全的.NETCore⾯试题(⼆)⼀.垃圾回收机制1. 简述⼀下⼀个引⽤对象的⽣命周期?(创建>使⽤>释放)new创建对象并分配内存对象初始化对象操作、使⽤资源清理(⾮托管资源)GC垃圾回收2. 创建下⾯对象实例,需要申请多少内存空间?public class User{public int Age { get; set; }public string Name { get; set; }public string _Name = "123" + "abc";public List<string> _Names;}40字节内存空间。
3. 什么是垃圾?⼀个变量如果在其⽣存期内的某⼀时刻已经不再被引⽤,那么,这个对象就有可能成为垃圾。
4. GC是什么,简述⼀下GC的⼯作⽅式?在公共语⾔运⾏时 (CLR) 中,垃圾回收器 (GC) ⽤作⾃动内存管理器。
垃圾回收器管理应⽤程序的内存分配和释放。
她的基本⼯作原理就是遍历托管堆中的对象,标记哪些被使⽤对象(哪些没⼈使⽤的就是所谓的垃圾),然后把可达对象转移到⼀个连续的地址空间(也叫压缩),其余的所有没⽤的对象内存被回收掉。
5. GC进⾏垃圾回收时的主要流程是?(1)标记,找到并创建所有活动对象的列表。
(2)重定位,⽤于更新对将要压缩的对象的引⽤。
(3)压缩,⽤于回收由死对象占⽤的空间,并压缩幸存的对象。
压缩阶段将垃圾回收中幸存下来的对象移⾄段中时间较早的⼀端。
6. GC在哪些情况下回进⾏回收⼯作?(1)系统具有低的物理内存。
这是通过 OS 的内存不⾜通知或主机指⽰的内存不⾜检测出来。
(2)由托管堆上已分配的对象使⽤的内存超出了可接受的阈值。
随着进程的运⾏,此阈值会不断地进⾏调整。
(3)调⽤ GC.Collect ⽅法。
⼏乎在所有情况下,你都不必调⽤此⽅法,因为垃圾回收器会持续运⾏。
此⽅法主要⽤于特殊情况和测试。
.NET垃圾回收器(GC)原理浅析作为.NET进阶内容的⼀部分,垃圾回收器(简称GC)是必须了解的内容。
本着“通俗易懂”的原则,本⽂将解释CLR中垃圾回收器的⼯作原理。
基础知识托管堆(Managed Heap)先来看MSDN的解释:初始化新进程时,运⾏时会为进程保留⼀个连续的地址空间区域。
这个保留的地址空间被称为托管堆。
“托管堆也是堆”,为什么这样说呢?这么说是希望⼤家不要被“术语”迷惑,这个知识点的前提是“值类型和引⽤类型的区别”。
这⾥假设读者已经知道“值类型存储在栈中,引⽤类型存储在堆中。
(引⽤类型的引⽤存储在栈中)”这⼀重要概念。
所以,根据这个理论,除值类型外,CLR要求所有资源都从托管堆分配。
托管堆维护着⼀个指针,这⾥命名为NextObjPtr,它指向下⼀个对象在堆中的分配位置。
CPU寄存器(CPU Register)这个是计算机基础知识,这⾥复习⼀下,有助于对下⾯“根”概念的理解。
CPU寄存器是CPU⾃⼰的”临时存储器”,⽐内存的存取还快。
按与CPU远近来分,离得最近的是寄存器,然后缓存(计算机⼀、⼆、三级缓存),最后内存。
根(Roots)类中定义的任何静态字段,⽅法的参数,局部变量(仅限引⽤类型变量)等都是根,另外cpu寄存器中的对象指针也是根。
根是CLR在堆之外可以找到的各种⼊⼝点。
对象可达与不可达(Objects reachable and unreachable)如果⼀个根引⽤了堆中的⼀个对象,则该对象为“可达”,否则即是“不可达”。
垃圾回收的原因从计算机组成的⾓度来讲,所有的程序都是要驻留在内存中运⾏的。
⽽内存是⼀个限制因素(⼤⼩)。
除此之外,托管堆也有⼤⼩限制。
如果托管堆没有⼤⼩限制,那C#的执⾏速度要优于c了(托管堆的结构让它有⽐c运⾏时堆更快的对象分配速度)。
因为地址空间和存储的限制因素,托管堆要通过垃圾回收机制,来维持它的正常运作,保证对象的分配,不会“内存溢出”。
垃圾回收的基本原理回收分为两个阶段:标记 –> 压缩标记的过程,其实就是判断对象是否可达的过程。
.netGC垃圾回收机制回收的是什么? .net开发⼈员都知道.net有个GC垃圾回收器,使开发⼈员不⽤再去花费⼼思考虑内存回收问题。
但是,有很多程序员并不清楚,垃圾回收器到底回收哪些资源。
数据类型分为值类型和引⽤类型,前者的数据存放在栈中,后者的数据存放在堆中。
俩种数据类型的使⽤⽆处不在,故很多程序员都错以为栈中和堆中的资源都会回收,这种观点就错误了!⼀般没研究过垃圾回收机制的程序员都很容易陷⼊这个误区。
那.net垃圾回收器到底回收哪些资源呢? .netGC垃圾回收器,只回收托管堆中的内存资源,不回收其他资源(数据库连接、⽂件句柄、⽹络端⼝等)。
[这⾥解释下什么是托管代码:由CLR来执⾏的代码] 下⾯就以最简洁的⽅式介绍下.net 垃圾回收: .NET Framework 的垃圾回收器管理应⽤程序的内存分配和释放。
每当您使⽤new 运算符创建对象时,公共语⾔运⾏时都会从托管堆为该对象分配内存。
只要托管堆中有地址空间可⽤,运⾏时就会继续为新对象分配空间。
但是,内存不是⽆限⼤的。
最终,垃圾回收器必须执⾏回收以释放⼀些内存。
垃圾回收器优化引擎根据正在进⾏的分配情况确定执⾏回收的最佳时间。
当垃圾回收器执⾏回收时,它检查托管堆中不再被应⽤程序使⽤的对象并执⾏必要的操作来回收它们占⽤的内存。
1、⾸先垃圾回收是CLR(公共语⾔运⾏时)的⼀个核⼼功能。
2、⽬的就是提⾼内存利⽤率。
3、只回收托管堆中的内存资源。
托管堆中没有变量引⽤的对象,表⽰可以被回收了(null)。
4、回收时间是不确定的,当程序需要新内存的时候开始执⾏回收。
Net 垃圾回收处理的基本方法CLR垃圾回收器根据所占空间大小划分对象。
大对象和小对象的处理方式有很大区别。
比如内存碎片整理——在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。
大对象堆和垃圾回收在.Net 1.0和2.0中,如果一个对象的大小超过85000byte,就认为这是一个大对象。
这个数字是根据性能优化的经验得到的。
当一个对象申请内存大小达到这个阀值,它就会被分配到大对象堆上。
这意味着什么呢?要理解这个,我们需要理解.Net垃圾回收机制。
如大多人所知道的,.Net GC是按照―代‖来回收的。
程序中的对象共有3代,0代、1代和2代,0代是最年轻的对象,2代对象存活的时间最长。
GC按代回收垃圾也是出于性能考虑的;通常的对象都会在0代是被回收。
例如,在一个程序中,和每一个请求相关的对象都应该在请求结束时回收掉。
而没有被回收的对象会成为1代对象;也就是说1代对象是常驻内存对象和马上消亡对象之间的一个缓冲区。
从代的角度看,大对象属于2代对象,因为只有在2代回收时才会处理大对象。
当某代垃圾回收执行时,会同时执行更年轻代的垃圾回收。
比如:当1代垃圾回收时会同时回收1代和0代的对象,当2代垃圾回收时会执行1代和0代的回收.代是垃圾回收器区分内存区域的逻辑视图。
从物理存储角度看,对象分配在不同的托管堆上。
一个托管堆(managed heap)是垃圾回收器从操作系统申请的内存区(通过调用windows api VirtualAlloc)。
当CLR载入内存之后,会初始化两个托管堆,一个大对象堆(LOH –large object heap)和一个小对象对(SOH – small object heap)。
内存分配请求就是将托管对象放到对应的托管堆上。
如果对象的大小小于85000byte,它会被放置在SOH;否则会被放在LOH上。
对于SOH,对象在执行一次垃圾回收之后,会进入到下一代。
.net垃圾回收机制原理
.NET Framework中的垃圾回收机制是通过自动内存管理来实
现的。
其工作原理如下:
1. 分配内存:当程序创建一个对象时,垃圾回收器会在内存堆上分配一块内存来存储该对象。
2. 标记阶段:在程序执行过程中,垃圾回收器会定期(或在特定条件下)进行自动垃圾回收。
在垃圾回收时,垃圾回收器会标记所有还在使用的对象。
它会从根节点(如全局变量、静态变量等)开始遍历对象图,标记所有可达的对象。
3. 清理阶段:在标记阶段之后,垃圾回收器会扫描堆内存中未标记的对象,并且将其清理。
这些未被标记的对象通常是不再被程序使用的对象,也被称为垃圾对象。
4. 内存整理:在清理阶段之后,垃圾回收器会对内存进行整理,将存活的对象移动到一起,以便有序分配连续的内存块。
.NET垃圾回收器使用的是代际回收算法,即将内存分为不同
的代际(Generation),每个代际维护一个分代表。
垃圾回收
器会根据对象在内存中的存活时间将其移动到不同的代际中。
新创建的对象通常被分配在第0代中,而经过多次回收仍然存活的对象会被移动到下一代。
这样一来,垃圾回收器优先扫描和清理新生成的对象,而不需要遍历整个堆内存,提高了垃圾回收效率。
总结起来,.NET垃圾回收器的原理是通过自动标记和清理未被使用的对象,并对内存进行整理,以便重新使用。
这种自动内存管理机制大大减少了开发者手动释放内存的工作量,提高了程序的性能和可靠性。
浅析.NET的垃圾回收机制作者:施俊李艳会来源:《电脑知识与技术》2008年第34期摘要:.NET Framework中的垃圾回收(Garbage Collection)机制减少了一些应用程序中发生内存泄露和访问冲突的可能性,但GC的工作机制仍然存在问题。
在程序开发中,总会发现对于GC的错误理解。
文章针对此问题进行研究分析,简要说明内存垃圾的产生原因、垃圾回收机制的原理、垃圾回收的主要对象以及释放模式。
关键词:内存管理;托管堆;垃圾回收;释放模式;终结器中图分类号:TP311 文献标识码:A文章编号:1009-3044(2008)34-1989-03Analyst of the Garbage Collection System of .NETSHI Jun, LI Yan-hui(Computer Science and Technology Department, Yangzhou Vocational College of Environment & Resources, Yangzhou 225127, China)Abstract: The garbage collection (GC) system of .NET Framework is to reduce the possibility of memory leak and access violation from some application programs, but the problem still exists because of the working system of the GC. It is always found the GC to be misunderstood during the program development. Based on the analysis of this problem, this article is going to explain briefly the root cause of memory garbage, the theory of the garbage collection system, its main objective and release mode.Key words: memory management; managed heap; garbage collection; release mode; finalizer1 引言我们可能都碰到过程序莫名奇妙的崩溃、或内存使用不停上涨。
.NET之垃圾回收机制GC⼀、GC的必要性 1、应⽤程序对资源操作,通常简单分为以下⼏个步骤:为对应的资源分配内存→初始化内存→使⽤资源→清理资源→释放内存。
2、应⽤程序对资源(内存使⽤)管理的⽅式,常见的⼀般有如下⼏种: [1] ⼿动管理:C,C++ [2] 计数管理:COM [3] ⾃动管理:.NET,Java,PHP,GO… 3、但是,⼿动管理和计数管理的复杂性很容易产⽣以下典型问题: [1] 程序员忘记去释放内存 [2] 应⽤程序访问已经释放的内存 产⽣的后果很严重,常见的如内存泄露、数据内容乱码,⽽且⼤部分时候,程序的⾏为会变得怪异⽽不可预测,还有Access Violation 等。
.NET、Java等给出的解决⽅案,就是通过⾃动垃圾回收机制GC进⾏内存管理。
这样,问题1⾃然得到解决,问题2也没有存在的基础。
总结:⽆法⾃动化的内存管理⽅式极容易产⽣bug,影响系统稳定性,尤其是线上多服务器的集群环境,程序出现执⾏时bug必须定位到某台服务器然后dump内存再分析bug所在,极其打击开发⼈员编程积极性,⽽且源源不断的类似bug让⼈厌恶。
⼆、GC是如何⼯作的 GC的⼯作流程主要分为如下⼏个步骤: 标记(Mark) →计划(Plan) →清理(Sweep) →引⽤更新(Relocate) →压缩(Compact) 1、标记 ⽬标:找出所有引⽤不为0(live)的实例 ⽅法:找到所有的GC的根结点(GC Root), 将他们放到队列⾥,然后依次递归地遍历所有的根结点以及引⽤的所有⼦节点和⼦⼦节点,将所有被遍历到的结点标记成live。
不会被考虑在内 2、计划和清理 [1] 计划 ⽬标:判断是否需要压缩 ⽅法:遍历当前所有的generation上所有的标记(Live),根据特定算法作出决策 [2] 清理 ⽬标:回收所有的free空间 ⽅法:遍历当前所有的generation上所有的标记(Live or Dead),把所有处在Live实例中间的内存块加⼊到可⽤内存链表中去 3、引⽤更新和压缩 [1] 引⽤更新 ⽬标:将所有引⽤的地址进⾏更新 ⽅法:计算出压缩后每个实例对应的新地址,找到所有的GC的根结点(GC Root), 将他们放到队列⾥,然后依次递归地遍历所有的根结点以及引⽤的所有⼦节点和⼦⼦节点,将所有被遍历到的结点中引⽤的地址进⾏更新,包括弱引⽤。
在.NET Framework中,内存中的资源(即所有二进制信息的集合)分为"托管资源"和"非托管资源".托管资源必须接受.NET Framework的CLR(通用语言运行时)的管理(诸如内存类型安全性检查),而非托管资源则不必接受.NET Framework的CLR管理. (了解更多区别请参阅.NET Framework或C#的高级编程资料)托管资源在.NET Framework中又分别存放在两种地方: "堆栈"和"托管堆"(以下简称"堆");规则是,所有的值类型(包括引用和对象实例)和引用类型的引用都存放在"堆栈"中,而所有引用所代表的对象实例都保存在堆中。
在.NET中,释放托管资源是可以自动通过"垃圾回收器"完成的(注意,"垃圾回收"机制是.NET Framework的特性,而不是C#的),但具体来说,仍有些需要注意的地方:1.值类型和引用类型的引用其实是不需要什么"垃圾回收器"来释放内存的,因为当它们出了作用域后会自动释放所占内存(因为它们都保存在"堆栈"中,学过数据结构可知这是一种先进后出的结构);2.只有引用类型的引用所指向的对象实例才保存在"堆"中,而堆因为是一个自由存储空间,所以它并没有像"堆栈"那样有生存期("堆栈"的元素弹出后就代表生存期结束,也就代表释放了内存),并且非常要注意的是,"垃圾回收器"只对这块区域起作用;3."垃圾回收器"也许并不像许多人想象的一样会立即执行(当堆中的资源需要释放时),而是在引用类型的引用被删除和它在"堆"中的对象实例被删除中间有个间隔,为什么呢? 因为"垃圾回收器"的调用是比较消耗系统资源的,因此不可能经常被调用;(当然,用户代码可以用方法System.GC.Collect()来强制执行"垃圾回收器")4.有析构函数的对象需要垃圾收集器两次处理才能删除:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象;5.由于垃圾收集器的工作方式,无法确定C#对象的析构函数何时执行;6.可实现IDisposable接口的Dispose()来显示释放由对象使用的所有未托管资源;7.垃圾收集器在释放了它能释放的所有对象后,就会压缩其他对象,把他们都移动回托管堆的端部,再次形成一个连续的块。
通俗易懂.NETGC垃圾回收机制(适⽤于⼩⽩⾯试,⼤⽜勿喷)情景:你接到xx公司⾯试邀请,你怀着激动忐忑的⼼坐在对⽅公司会议室,想着等会的技术⾯试。
技术总监此时⾛来,与你简单交谈后....技术:你对GC垃圾回收机制了解的怎么样?你:还⾏,有简单了解过。
技术:那我考考你...⼀、GC全称答:Garbage Collector(垃圾收集器)⼆、GC有什么⽤?(垃圾回收的⽬的)答:主要就是为了提⾼内存利⽤率三、GC回收哪些垃圾?答:只回收“托管堆中的内存资源”,没有引⽤的对象。
不回收“栈”上的资源(⽐如值类型)。
四、什么时候回收?答:不确定,当程序需要新内存的时候执⾏回收。
五、GC垃圾回收机制(重点!)答:引⼊“代”的概念,①总共有三代,0~2代。
②每次新建对象都在第0代中。
③每代有固定⼤⼩。
图解:新建了a、b、c、d四个对象,第0代内存满了,但此时⼜新建了⼀个e对象,那这个时候会发⽣什么?e对象会被放在哪⾥?1代⾥吗?还是2代?错!不会放在1代⾥,也不会放在2代⾥。
这个时候会触发“垃圾回收”,CLR会去遍历这a、b、c、d这四个对象,打上标记。
假设a和b对象已经没有被引⽤了,c和d还在被引⽤,那a和b就会被当做垃圾回收掉,c和d就会被放到1代⾥,e也就进⼊0代了。
进过GC回收后,就变成了下⾯这样:如此往复。
备注:这个叫“⾃动回收”,肯定有⼩朋友会问,那是不是可以⼿动回收。
答案是肯定的,⽐如析构函数就可以达到这个⽬的。
还不知道什么是“析构函数”的⼩朋友,请偷偷出门去⾃⾏百度。
六、弱引⽤⼈就是这样,得不到的永远在骚动,得到的有恃⽆恐。
当失去了,才追悔莫及。
假如对象要被回收了,但是我⼜想再次⽤这个对象,该怎么办?这个时候就出来了这个--“弱引⽤”。
为什么需要弱对象呢?因为,有⼀些数据创建起来很容易,但是却需要很多内存。
例如:有⼀个程序需要去访问⽤户硬盘上的所有⽂件夹和⽂件名;你可以在程序第⼀次需要这个数据时访问⽤户磁盘⽣成⼀次数据,数据⽣成之后你就可以访问内存中的数据来得到⽤户⽂件数据,⽽不是每次都去读磁盘获得数据,这样做可以提升程序的性能。
深入理解.NET内存回收机制
[前言:].Net平台提供了许多新功能,这些功能能够帮助程序员生产出更高效和稳定的代码。
其中之一就是垃圾回收器(GC)。
这篇文章将深入探讨这一功能,了解它是如何工作的以及如何编写代码来更好地使用这一.Net平台提供的功能。
.Net中的内存回收机制
垃圾回收器是用来管理应用程序的内存分配和释放的。
在垃圾回收器出现以前,程序员在使用内存时需要向系统申请内存空间。
有些语言,例如Visual Basic,可以自动完成向系统申请内存空间的工作。
但是在诸如Visual C++的语言中要求程序员在程序代码中申请内存空间。
如果程序员在使用了内存之后忘了释放内存,则会引起内存泄漏。
但是有了垃圾回收器,程序员就不必关心内存中对象在离开生存期后是否被释放的问题。
当一个应用程序在运行的时候,垃圾回收器设置了一个托管堆。
托管堆和C语言中的堆向类似,但是程序员不需要从托管堆中释放对象,并且在托管堆中对象的存放是连续的。
每次当开发人员使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。
新创建的对象被放在上次创建的对象之后。
垃圾回收器保存了一个指针,该指针总是指向托管堆中最后一个对象之后的内存空间。
当新的对象被产生时,运行库就知道应该将新的对象放在内存的什么地方。
同时开发人员应该将相同类型的对象放在一起。
例如当开发人员希望向数据库写入数据的时侯,首先需要创建一个连接对象,然后是Command对象,最后是DataSet对象。
如果这些对象放在托管堆相邻的区域内,存取它们就非常快。
当垃圾回收器的指针指向托管堆以外的内存空间时,就需要回收内存中的垃圾了。
在这个过程中,垃圾回收器首先假设在托管堆中所有的对象都需要被回收。
然后它在托管堆中寻找被根对象引用的对象(根对象就是全局,静态或处于活动中的局部变量以及寄存器指向的对象),找到后将它们加入一个有效对象的列表中,并在已经搜索过的对象中寻找是否有对象被新加入的有效对象引用。
直到垃圾回收器检查完所有的对象后,就有一份根对象和根对象直接或间接引用了的对象的列表,而其它没有在表中的对象就被从内存中回收。
当对象被加入到托管堆中时,如果它实现了finalize()方法,垃圾回收器会在它的终结列表(Finalization List)中加入一个指向该对象的指针。
当该对象被回收时,垃圾回收器会检查终结列表,看是否需要调用对象的finalize()方法。
如果有的话,垃圾回收器将指向该对象的指针加入一个完成器队列中,该完成器队列保存了那些准备调用finalize()方法的对象。
到了这一步对象还不是真正的垃圾对象。
因此垃圾回收器还没有把他们从托管堆中回收。
当对象准备被终结时,另一个垃圾回收器线程会调用在完成器队列中每个对象的finalize()方法。
当调用完成后,线程将指针从完成器队列中移出,这样垃圾回收器就知道在下一次回收对象时可以清除被终结的对象了。
从上面可以看到垃圾回收机制带来的很大一部分额外工作就是调用finalize()方法,因此在实际编程中开发人员应该避免在类中实现finalize()方法。
对于finalize()方法的另一个问题是开发人员不知道什么时候它将被调用。
它不像C++中的析构函数在删除一个对象时被调用。
为了解决这个问题,在.Net中提供了一个接口IDisposable。
微软建议在实现带有fianlize()方法的类的时侯按照下面的模式定义对象:
public class Class1 : IDisposable
{
public Class1()
{
}
~Class1 ()
{
//垃圾回收器将调用该方法,因此参数需要为false。
Dispose (false);
}
//该方法定义在IDisposable接口中。
public void Dispose ()
{
//该方法由程序调用,在调用该方法之后对象将被终结。
//因为我们不希望垃圾回收器再次终结对象,因此需要从终结列表中去除该对象。
GC.SuppressFinalize (this);
//因为是由程序调用该方法的,因此参数为true。
Dispose (true);
}
//所有与回收相关的工作都由该方法完成
private void Dispose(bool disposing)
{
lock(this) //避免产生线程错误。
{
if (disposing)
{
//需要程序员完成释放对象占用的资源。
}
//对象将被垃圾回收器终结。
在这里添加其它和清除对象相关的代码。
}
}
}
现在我们了解了垃圾回收器工作的基本原理,接下来让我们看一看垃圾回收器内部是如何工作的。
目前有很多种类型的垃圾回收器。
微软实现了一种生存期垃圾回收器(Generational Garbage Collector)。
生存期垃圾回收器将内存分为很多个托管堆,每一个托管堆对应一种生存期等级。
生存期垃圾回收器遵循着下面的原则:
新生成的对象,其生存期越短;而对象生成时间越长的对象,其生存期也就越长。
对于垃圾回收器来说,回收一部分对象总是比回收全部对象要快,因此垃圾回收器对于那些生存期短的对象回收的频率要比生存期长的对象的回收频率高。
.Net中的垃圾回收器中目前有三个生存期等级:0,1和2。
0、1、2等级对应的托管堆的初始化大小分别是256K,2M和10M。
垃圾回收器在发现改变大小能够提高性能的话,会改变托管堆的大小。
例如当应用程序初始化了许多小的对象,并且这些对象会被很快回收的话,垃圾回收器就会将0等级的托管堆变为128K,并且提高回收的频率。
如果情况相反,垃圾回收器发现在0等级的托管堆中不能回收很多空间时,就会增加托管堆的大小。
在应用程序初始化的之前,所有等级的托管堆都是空的。
当对象被初始化的时候,他们会按照初始化的先后顺序被放入等级为0的托管堆中。
在托管堆中对象的存放是连续的,这样使得托管堆存取对象的速度很快,因为托管对不必对内存进行搜索。
垃圾回收器中保存了一个指针指向托管堆中最后一个对象之后的内存空间。
图一中显示了一个包含四个对象的0等级的托管堆。
图一包含四个对象的托管堆
当0等级托管堆被对象填满后,例如候程序初始化了新的对象,使0等级托管堆的大小超过了256K,垃圾回收器会检查托管堆中的所有对象,看是否有对象可以回收。
当开始回收操作时,如前面提到的,垃圾回收器会找出根节点和根节点直接或间接引用了的对象,然后将这些对象转移到1等级托管堆中,并将0等级托管堆的指针移到最开始的位置以清除所有的对象。
同时垃圾回收器会压缩1等级托管堆以保证所有对象之间没有内存空隙。
当1等级托管堆满了之后,会将对象转移到2等级的托管堆。
例如在图一之后,垃圾回收器开始回收对象,假定D对象将被回收,同时程序创建了E和F 对象。
这时候托管堆中的对象如图二所示。
图二回收对象后的0等级和1等级托管堆
然后程序创建了新的对象G和H,再一次触发了垃圾回收器。
对象E将被回收。
这时候托管堆中的对象如图三所示。
生存期垃圾回收器的原则也有例外的情况。
当对象的大小超过84K时,对象会被放入"大对象区"。
大对象区中的对象不会被垃圾回收器回收,也不会被压缩。
这样做是为了强制垃圾回收器只能回收小对象以提高程序的性能。
控制垃圾回收器
在.Net框架中提供了很多方法使开发人员能够直接控制垃圾回收器的行为。
通过使用GC.Collect()或GC.Collect(int GenerationNumber)开发人员可以强制垃圾回收器对所有等级的托管堆进行回收操作。
在大多数的情况下开发人员不需要干涉垃圾回收器的行为,但是有些情况下,例如当程序进行了非常复杂的操作后希望确认内存中的垃圾对象已经被回收,就可以使用上面的方法。
另一个方法是GC.WaitForPendingFinalizers(),它可以挂起当前线程,直到处理完成器队列的线程清空该队列为止。
使用垃圾回收器最好的方法就是跟踪程序中定义的对象,在程序不需要它们的时候手动释放它们。
例如程序中的一个对象中有一个字符串属性,该属性会占用一定的内存空间。
当该属性不再被使用时,开发人员可以在程序中将其设定为null,这样垃圾回收器就可以回收该字符串占用的空间。
另外,如果开发人员确定不再使用某个对象时,需要同时确定没有其它对象引用该对象,否则垃圾回收器不会回收该对象。
另外值得一提的是finalize()方法应该在较短的时间内完成,这是因为垃圾回收器给finalize ()方法限定了一个时间,如果finalize()方法在规定时间内还没有完成,垃圾回收器会终止运行finalize()方法的线程。
在下面这些情况下程序会调用对象的finalize()方法:
0等级垃圾回收器已满
程序调用了执行垃圾回收的方法
公共语言运行库正在卸载一个应用程序域
公共语言运行库正在被卸载。