.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的大小也有变化。
.Net垃圾收集机制—了解算法与代龄的更多相关文章
- JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)
相信和小编一样的程序猿们在日常工作或面试当中经常会遇到JVM的垃圾回收问题,有没有在夜深人静的时候详细捋一捋JVM垃圾回收机制中的知识点呢?没时间捋也没关系,因为小编接下来会给你捋一捋. 一. 技术 ...
- CLR内存回收总结,代龄机制
关键字:对象可达图,代龄机制,终止化对象. 代龄机制: 0代满了之后,GC开始回收,剩下的对象升级为1代.(只有不可达的对象才会被回收.) 0代再回收几次之后,1代的对象慢慢增多然后达到阈值,GC同时 ...
- .Net Discovery系列之三 深入理解.Net垃圾收集机制(上)
前言: 组成.Net平台一个很重要的部分----垃圾收集器(Garbage Collection),今天我们就来讲讲它.想想看没有GC,.Net还能称之为一个平台吗?各种语言虽然都被编译成MSIL,但 ...
- .Net Discovery 系列之七--深入理解.Net垃圾收集机制(拾贝篇)
关于.Net垃圾收集器(Garbage Collection),Aicken已经在“.Net Discovery 系列”文章中有2篇的涉及,这一篇文章是对上2篇文章的补充,关于“.Net Discov ...
- JVM垃圾收集策略与算法
垃圾收集策略与算法 程序计数器.虚拟机栈.本地方法栈随线程而生,也随线程而灭:栈帧随着方法的开始而入栈,随着方法的结束而出栈.这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的 ...
- [转帖]JVM—深入理解内存模型与垃圾收集机制
JVM—深入理解内存模型与垃圾收集机制 https://juejin.im/post/5d68dc9ee51d4561ad6548f7 前言 Java是一种跨平台的语言,当初其设计初衷也是为了解决各个 ...
- Java 虚拟机系列二:垃圾收集机制详解,动图帮你理解
前言 上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容--垃圾收集.众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存 ...
- JVM垃圾收集机制
JVM垃圾回收机制是java程序员必须要了解的知识,对于程序调优具有很大的帮助(同时也是大厂面试必问题). 要了解垃圾回收机制,主要从三个方面: (1)垃圾回收面向的对象是谁? (2)垃圾回收算法有哪 ...
- JVM学习七-(复习)垃圾收集策略与算法
垃圾收集策略与算法 程序计数器.虚拟机栈.本地方法栈随线程而生,也随线程而灭:栈帧随着方法的开始而入栈,随着方法的结束而出栈.这几个区域的内存分配和回收都具有确定性,在这几个区域内不需要过多考虑回收的 ...
随机推荐
- 在centOS使用systemctl配置启动多个tomcat
公司服务器使用的是阿里云CentOS7,CentOS7和CentOS6目前最大区别就是service变成了现在的systemctl,简单的查了一下并结合使用,发现systemctl功能上等同于6上面的 ...
- Ninject
一.为什么要使用依赖注入框架 依赖注入框架也叫IoC容器.它的作用使类与类之间解耦 我们看看为什么要用依赖注入框架,举个几个梨子: 1,高度耦合的类 有一个Order类,Order类是用于订单操作的, ...
- python 文件内容修改替换操作
当我们读取文件中内容后,如果想要修改文件中的某一行或者某一个位置的内容,在python中是没有办法直接实现的,如果想要实现这样的操作只能先把文件所有的内容全部读取出来,然后进行匹配修改后写入到新的文件 ...
- Mysql表连接查询
原文地址: https://www.cnblogs.com/qiuqiuqiu/p/6442791.html 1.内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符).包括相等 ...
- [ 原创 ] Java基础5--abstract class和interface的区别
1.含有abstract抽象修饰符的类就是抽象类.abstract 类不能创建实例对象 2.含有abstract方法的类必须定义为abstract class,abstract class类中的方法不 ...
- 通过TortoiseGit上传项目到GitHub
1.安装msysgit和TortoiseGit : 2.TortoiseGit 设置: (1).确保安装成功: (2).设置用户名和邮箱: 3.登陆github并进入设置页面: 4.添加 SSH Ke ...
- bzoj4974 字符串大师 KMP
明显的,有$next[i] = i - pre[i]$ 根据$next[i]$构造比根据$pre[i]$简单 如果$next[i] \neq 0$,那么我们可以直接取前面的结果 否则,我们可以暴力的寻 ...
- 开源中国上抓取的content-type
开源中国上抓取的content-type类型,来源:http://www.cnblogs.com/smallyard/p/5632608.html { ".*": "ap ...
- setTimeout 第一个参数类型
读别人代码的时候看到这么一段,很不理解,然后就搜了一下百度 setTimeout / setInterval 第一个参数可以有三种类型: 字符串 . methods . 匿名函数 1.字符串 ...
- lua协程----ngx-lua线程学习笔记
--[[ - @desc lua数据输出 - @param string 字符串 - return string --]] function dump(v) if not __dump then fu ...