.Net垃圾收集机制—了解算法与代龄

垃圾收集器在本质上就是负责跟踪所有对象被引用到的地方,关注对象不再被引用的情况,回收相应的内存。在.NET平台中同样如此,有效的提高.NET垃圾回收性能,能够提高程序执行效率。

  其实垃圾收集并不是伴随Java出现的,早在1958年,图林奖得主John发明的Lisp语言就已经提供了GC的功能,这是GC的第一次出现,是思想的一次闪光!而后,1984年Dave Ungar发明的Small talk语言第一次正式采用了GC机制。.Net的垃圾回收机制是个很大的话题,如果你没接触过类似C++那样的语言,就很难理解GC是一个多么重要、令人兴奋的东西:

  1.提高软件系统的内聚。

  2.降低编程复杂度,使程序员不必分散精力去处理析构。

  3.不妨碍设计师进行系统抽象。

  4.减少由于内存运用不当产生的Bug。

  5.成功的将内存管理工作从程序的编写时,脱离至运行时,使不可预估的管理漏洞变为可预估的。

  1.算法

  垃圾收集器的本质,就是跟踪所有被引用到的对象,整理对象不再被引用的对象,回收相应的内存。这听起来类似于一种叫做“引用计数(Reference Counting)”的算法,然而这种算法需要遍历所有对象,并维护它们的引用情况,所以效率较低些,并且在出现“环引用”时很容易造成内存泄露。所以.Net中采用了一种叫做“标记与清除(Mark Sweep)”算法来完成上述任务。“标记与清除”算法,顾名思义,这种算法有两个本领:

  “标记”本领——垃圾的识别:从应用程序的root出发,利用相互引用关系,遍历其在Heap上动态分配的所有对象,没有被引用的对象不被标记,即成为垃圾;存活的对象被标记,即维护成了一张“根-对象可达图”。其实,CLR会把对象关系看做“树图”,无疑,了解数据结构的同学都知道,有了“树图”的概念,会加快遍历对象的速度。   检测、标记对象引用,是一件很有意思的事情,有很多方法可以做到,但是只有一种是效率最优的,.Net中是利用栈来完成的,在不断的入栈与出栈中完成检测:先在树图中选择一个需要检测的对象,将该对象的所有引用压栈,如此反复直到栈变空为止。栈变空意味着已经遍历了这个局部根(或者说是树图中的节点)能够到达的所有对象。树图节点范围包括局部变量(实际上局部变量会很快被回收,因为它的作用域很明显、很好控制)、寄存器、静态变量,这些元素都要重复这个操作。一旦完成,便逐个对象地检查内存,没有标记的对象变成了垃圾。

  “清除”本领——回收内存:启用Compact算法,对内存中存活的对象进行移动,修改它们的指针,使之在内存中连续,这样空闲的内存也就连续了,这就解决了内存碎片问题,当再次为新对象分配内存时,CLR不必在充满碎片的内存中寻找适合新对象的内存空间,所以分配速度会大大提高。

  但是大对象(large object heap)除外,GC不会移动一个内存中巨无霸,因为它知道现在的CPU不便宜。通常,大对象具有很长的生存期,当一个大对象在.NET托管堆中产生时,它被分配在堆的一个特殊部分中,移动大对象所带来的开销超过了整理这部分堆所能提高的性能。

  Compact算法除了会提高再次分配内存的速度,如果新分配的对象在堆中位置很紧凑的话,高速缓存的性能将会得到提高,因为一起分配的对象经常被一起使用(程序的局部性原理),所以为程序提供一段连续空白的内存空间是很重要的。

  2.代龄(Generation)  

 代龄就是对Heap中的对象按照存在时间长短进行分代,最短的分在第0代,最长的分在第2代,第2代中的对象往往是比较大的。Generation的层级与FrameWork版本有关,可以通过调用GC.MaxGeneration得知。   通常,GC会优先收集那些最近分配的对象(第0代),这与操作系统经典内存换页算法“最近最少使用”算法如出一辙。但是,这并不代表GC只收集最近分配的对象,通常,.Net GC将堆空间按对象的生存期长短分成3代:新分配的对象在第0代(0代空间最大长度通常为256K),按地址顺序分配,它们通常是一些局部变量;第1代(1代空间最大长度通常为2 MB)是经过0代垃圾收集后仍然驻留在内存中的对象,它们通常是一些如表单,按钮等对象;第2代是经历过几次垃圾收集后仍然驻留在内存中的对象,它们通常是一些应用程序对象。

  当内存吃紧时(例如0代对象充满),GC便被调入执行引擎--也就是CLR--开始对第0代的空间进行标记与压缩工作、回收工作,这通常小于1毫秒。如果回收后内存依然吃紧,那么GC会继续回收第1代(回收操作通常小于10毫秒)、第2代,当然GC有时并不是按照第0、1、2代的顺序收集垃圾的,这取决于运行时的情况,或是手动调用GC.Collect(i)指定回收的代。当对第2代回收后任然无法获得足够的内存,那么系统就会抛出OutOfMemoryException异常,当经过几次GC过后,0代中的某个对象仍然存在,那么它将被移动到第1代。同理,第1、2代也按同样的逻辑运行。   这里还要说的是,GC Heap中代的数量与容量,都是可变的(这由一个“策略引擎”控制,在第二节中,会介绍到“策略引擎”), 以下代码结合Windbg可以说明这个问题,以下代码中,可以通过单击按钮“button1”,不断的分配内存,而后获得对象“a”的代龄情况,并且在Form加载时也会获得“a”的代龄。   1.public partial class Form

1 : Form

  2.{

  3. private string a = new string(a,1);

  4. public Form1()

  5. {

  6. InitializeComponent();

  7. }

  8. private void button1_Click(object sender, EventArgs e)

  9. {

  10. a = new string(a, 900000);

  11. label1.Text = GC.GetGeneration(a).ToString();

  12. }

  13. private void Form1_Load(object sender, EventArgs e)

  14. {

  15. label1.Text = GC.GetGeneration(a).ToString();

  16. }

  17.}

  程序刚加载时,“a”的代龄为第0代,通过windbg我们还获得了以下信息:

  可以看出,GC堆被分成了两个段,三代,每代起始地址十进制差值为12,点击数次“button1”按钮后,“a”的代龄升为第2代,通过windbg我们又获得了以下信息:

  这里要注意一个很关键的地方,就是各代的起始(generation x starts at)十进制地址差值不再是12,0代与1代差为98904,1代与2代差为107908,这说明代的大小随程序运行在改变,并且GC heap的大小也有变化。

时间: 2024-11-13 22:48:10

.Net垃圾收集机制—了解算法与代龄的相关文章

JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)

如果想了解Java内存模型参考:jvm内存模型-和内存分配以及jdk.jre.jvm是什么关系(阿里,美团,京东) 相信和小编一样的程序猿们在日常工作或面试当中经常会遇到JVM的垃圾回收问题,有没有在夜深人静的时候详细捋一捋JVM垃圾回收机制中的知识点呢?没时间捋也没关系,因为小编接下来会给你捋一捋. 一. 技术背景你要了解吧 二. 哪些内存需要回收? 2.1 引用计数算法 2.1.1 算法分析 2.1.2 优缺点 2.1.3 是不是很无趣,来段代码压压惊 2.2 可达性分析算法 2.3 Jav

.Net Discovery系列之三 深入理解.Net垃圾收集机制(上)

前言: 组成.Net平台一个很重要的部分----垃圾收集器(Garbage Collection),今天我们就来讲讲它.想想看没有GC,.Net还能称之为一个平台吗?各种语言虽然都被编译成MSIL,但是运行时的资源回收工作却“各自为战”,这样不但增加了编程难度,也会使内存管理工作变得复杂无比(不同语言处理内存的微小差异,将在回收资源时被放大),更也不利于平台移植. 这篇文章将全面的为大家介绍.Net 垃圾收集的运行方式.算法,以及与垃圾收集相关的关键方法. 说到垃圾收集机制,很少有人知道,垃圾收

第一节 垃圾收集机制简介以及简单配置

垃圾收集机制(GC)是JVM用于释放那些不再使用的对象所占用内存的程序和算法.GC并没有写入在java语言定制的标准中,因此并不是所有的JVM都有GC.GC的主要目的就是清除不再使用的对象. 垃圾回收的两种方法: 1.引用计数 引用计数表示一个对象被引用的所有次数,当引用计数为0时,则表示该对象没有被引用,可以将其删除. 2.对象引用树 目前比较常用的垃圾收集机制是对象引用树,即将对像的引用关系构建成一棵树,从一组根对象开始,对所有对象进行查找,通过递归查找若在该树中找到相应的对象,则将该对象标

.net垃圾收集机制【转】

在.NET Framework中,内存中的资源(即所有二进制信息的集合)分为"托管资源"和"非托管资源".托管资源必须接受.NET Framework的CLR(通用语言运行时)的管理(诸如内存类型安全性检查),而非托管资源则不必接受.NET Framework的CLR管理. (了解更多区别请参阅.NET Framework或C#的高级编程资料) 托管资源在.NET Framework中又分别存放在两种地方: "堆栈"和"托管堆"

【深入Java虚拟机】之二:Java垃圾收集机制

[深入Java虚拟机]之:Java垃圾收集机制 对象引用 Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了Java中所有的对象实例.谈到Java堆中的垃圾回收,自然要谈到引用.在JDK1.2之前,Java中的引用定义很很纯粹:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用.但在JDK1.2之后,Java对引用的概念进行了扩充,将其分为强引用(Strong Reference).软引用(Soft Reference).弱引用(

Java虚拟机(五)——垃圾收集机制

垃圾回收介绍 ?? Java虚拟机内存划分讲到了Java 内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊地执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来是就已知了.因此这几个区域的内存分配和回收都具有确定性,在这几个区域就需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了.而Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个

垃圾收集机制与内存分配策略

Java 语言与其他编程语言有一个非常突出的特点,自动化内存管理机制.而这种机制离不开高效率的垃圾收集器(Garbage Collection)与合理的内存分配策略,这也是本篇文章将要描述的两个核心点. 引一句周志明老师对 Java 中的内存管理机制的描述: Java 与 C++ 之间有一堵有内存动态分配和垃圾收集技术所围成的「高墙」,墙外面的人想进去,墙里面的人却想出来. 各有各的优势,没有谁会替代谁,只是应用在不同的场合下,谁更适合而已. 可达性分析算法 Java 中使用「可达性分析算法」来

小仙女讲JVM(3)—垃圾收集机制 񪬗

原文: http://blog.gqylpy.com/gqy/445 置顶:来自一名75后老程序员的武林秘籍--必读(博主推荐) 来,先呈上武林秘籍链接:http://blog.gqylpy.com/gqy/401/ 你好,我是一名极客!一个 75 后的老工程师! 我将花两分钟,表述清楚我让你读这段文字的目的! 如果你看过武侠小说,你可以把这个经历理解为,你失足落入一个山洞遇到了一位垂暮的老者!而这位老者打算传你一套武功秘籍! 没错,我就是这个老者! 干研发 20 多年了!我也年轻过,奋斗过!我

垃圾收集机制

当一个函数执行的时候,会有一个机制,给这个函数内部的所有变量都标上“我要进啦”,“开始啦”这种类似的标签. 当一个函数执行完毕之后,这个函数内部的所有不能再被访问到的变量都标上“离开了”,“这是垃圾了”. 最后垃圾收集器根据特定的时间或在算法不断的去看哪些变量被确定是垃圾了,然后把它们收集,这样就腾出更多内存来做其他操作. 这就是现在javascrip主流的垃圾收集机制 (标记清除) 函数内部哪些变量再执行完后还能被访问 如果理解闭包的话,就能理解在函数执行完之后内部的变量在什么时候还能被访问而