前言

今天我们来共同学习一下CLR的垃圾回收机制,这对我们写出健壮性的代码很有帮助,也许有人会认为多此一举,认为垃圾回收交给CLR就行,我不用关心这个,诚然,大多数情况下是这样的,但是,我们今天讨论的是程序的健壮性以及能够快速定位那些神出鬼没的问题。

一个例子

 static void Main(string[] args)
{
Timer timer = new Timer(OnTimer,null,0,1000);
Console.ReadLine();
} private static void OnTimer(object state)
{
Console.WriteLine(1);
}

看一下上面的代码,大家认为在release模式下,会打印出来几个1?

可能会有两种答案:

  • 无限多个,1s一个
  • 不确定几个

再看下列代码:

 static void Main(string[] args)
{
Timer timer = new Timer(OnTimer,null,0,1000);
Console.ReadLine();
} private static void OnTimer(object state)
{
Console.WriteLine(1);
GC.Collect();
}

这次能打印出来几个1呢?是不是还是两种答案呢?

这里我先说明一个问题,开始时我已经说过了,程序时在release下运行的,为什么我们要给出这个条件呢?因为,在debug模式下,编译器会延长局部变量的生命周期直至方法的结束,而release模式下,方法中的代码下没有再调用的变量生命周期都已结束,被认为可以回收的对象,明确这一点是十分重要的。

根据上面的阐述,你是不是已经认识到:第一个代码片段的答案是【不确定几个】,因为如果我们程序实例化了很多变量,导致进行了一次垃圾回收的工作,那么变量timer就会被释放掉;而第二个代码片段,是我写出的垃圾回收的极端情况,它的答案应该是:只打印出一个1.

是不是感觉有点惊讶?!接下来,我们将共同解开CLR垃圾回收机制的神秘面纱

垃圾回收的算法比较

对于所有的托管系统来说,垃圾回收机制的算法一般包含两种:

  • 引用计数器算法
  • 引用追踪算法

    我们先来讨论【引用计算器算法】的优缺点。该算法是在每个对象的实例都有一个内存空间来存储当前被多少对象引用,引用增加是就加1,超出变量作用域的就减一直至为0,就认为该对象可以被回收了,此种算法简单有效,但它不能解决循环引用的情况,如果a引用了b,b再引用了a(a,b为两个对象的实例),那么a和b永远不会被释放.

[引用追踪算法]它只关心堆上的对象是否有变量引用它,如果没有就认为是可以回收的对象。而CLR就是使用的这种垃圾回收算法,接下来,我们来共同学习一下这种算法在CLR中的应用

垃圾回收机制的步骤

一次垃圾回收一般分为三个步骤:

  • 标记
  • 回收
  • 压缩

标记

这一步的只要工作是找到堆上没有被变量引用的对象实例。引用对象在分配内存时都加了一个区块叫【同步块索引】,该索引占64位,8个字节(64位系统上),对堆上的对象进行标记时就是用了这一块区域的某一位。

  • 在开始标记之前,先把堆上的所有对象的这一位标记为0。
  • 堆上的对象有变量指向的,这一位改成1。这表示该对象时可达的
  • 标记工作结束后,对象的【同步块索引】那一位标记为0的,就代表时可以回收的对象

标记工作的模式

标记对象的工作有两种模式:

  • 同步 :标记工作开始之处,就暂停所有线程,开始标记工作
  • 并发 :起一个低优先级的线程执行标记工作,直到找到有为0的对象,再暂停所有线程,进行垃圾回收工作

回收

回收工作就很简单了,在堆上删除掉标记为0 的对象

压缩

对象被删除后,会导致内存空间有碎片,这个时候CLR就会执行一次压缩工作,将不连续的内存使用,变成连续的;压缩后,变量的引用地址和堆上对象分配的空间地址不对应了,为了解决这个问题,CLR又执行了一次引用地址的偏移修改。之后再启动所有被暂停的线程,一次垃圾回收就执行完毕了!

垃圾回收机制的优化

上一节讲的垃圾回收机制有一个大的性能问题,它每次执行标记工作时都要扫描一遍堆上的所有对象,这是就产生了一个性能问题,微软为了解决这个问题,提出了代的概念,首先他给出了一下假设:

  • 对象越新,生存期越短
  • 对象越老,生存期越长
  • 回收堆的一部分,速度快于回收整个堆

三世同堂

CLR只支持最多3代的对象。0代、1代、2代

在CLR初始化时,CLR会对这三代回收对象各自预留一个空间,当每个代中的对象超出整个空间时,就会执行一次垃圾回收。CLR会根据程序执行情况动态的调整这三个预留空间的大小,这里我们不去了解这种动态调整的情况,接下来我们来说一下怎么产生的0、1、2代对象以及它们怎么被回收的

垃圾回收基于代的优化

  1. CLR初始化后,只有0代的对象
  2. 随着应用程序的使用,堆上0代对象的内存空间超出了CLR为其预留的空间,就会进行一次垃圾回收
  3. 本次垃圾回收,留存下来的对象,会变成1代对象
  4. 循环执行2,3步骤,当1代对象达到预留空间时,CLR会进行1代和0代对象的垃圾回收
  5. 本次垃圾回收留存下来的1代对象,变成2代对象
  6. 循环执行2,3,4,5,当2代对象达到预留空间时,CLR会进行三代对象的垃圾回收

垃圾回收的其他知识点

  • 应用程序可以强制对所有代的对象进行垃圾,需要使用 GC.Collect();Collect方法有5个重载
  • 针对大对象(85000字节以上),CLR单独在对上分配一块内存区域,其对象总是2代对象,因此,我们应该确保大对象的生命周期应该很长,否则CLR频繁对2代对象进行回收,会降低性能
  • ~ClassName(),析构函数总是在垃圾回收后执行,因此存在析构函数的对象总会被留存到下一代进行垃圾回收

一文带你吃透CLR垃圾回收机制的更多相关文章

  1. CLR 垃圾回收算法

    c#相较于c,c++而言,在内存管理上为程序员提供了极大的方便,解放了程序员与内存地址打交道,提高了程序员的工作效率.比如c中分配的malloc堆空间没有释放导致的内存泄露,数组越界导致的踩内存错误, ...

  2. 深度好文:PHP写时拷贝与垃圾回收机制(转)

    原文地址:http://www.php100.com/9/20/87255.html 写入拷贝(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略.其核心思想是,如果有多个调用 ...

  3. .NET垃圾回收机制 转

    在.NET Framework中,内存中的资源(即所有二进制信息的集合)分为"托管资源"和"非托管资源".托管资源必须接受.NET Framework的CLR( ...

  4. JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...

  5. python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...

  6. Javascript 垃圾回收机制

    转载于https://www.cnblogs.com/zhwl/p/4664604.html 一.垃圾回收的必要性 由于字符串.对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储 ...

  7. golang 垃圾回收机制

    用任何带 GC 的语言最后都要直面 GC 问题.在以前学习 C# 的时候就被迫读了一大堆 .NET Garbage Collection 的文档.最近也学习了一番 golang 的垃圾回收机制,在这里 ...

  8. java有自动垃圾回收机制

    当垃圾收集器判断已经没有任何引用指向对象的时候,会调用对象的finalize方法来释放对象占据的内存空间~ java中垃圾回收以前听老师讲好像是内存满了他才去做一次整体垃圾回收,在回收垃圾的同时会调用 ...

  9. (IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    参考博客: https://www.cnblogs.com/xiao987334176/p/9056511.html 内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yi ...

随机推荐

  1. 关于dp那些事

    拿到一道题,先写出状态转移方程,再优化时间复杂度 状态优化: 对于状态可累加 \(e.g.dp[i+j]=dp[i]+dp[j]+i+j\) 的,用倍增优化 决策优化: \(e.g.dp[i][j]= ...

  2. 一文彻底搞通TCP之send & recv原理

    接触过网络开发的人,大抵都知道,上层应用使用send函数发送数据,使用recv来接收数据,而send和recv的实现原理又是怎样的呢? 在前面的几篇文章中,我们有提过,TCP是个可靠的.全双工协议.其 ...

  3. Python实现可视化操作

    # Author kevin_hou #简单的GUI文本编辑器 from tkinter import * from tkinter.scrolledtext import ScrolledText ...

  4. Python设置Excel样式

    前面已经详细讲解过使用Python对Excel表格进行读.写操作,本文主要讲解下使用Python设置Excel表格的样式. 深入学习请参考openpyxl官方文档: https://openpyxl. ...

  5. Redis:学习笔记-04

    Redis:学习笔记-04 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 10. Redis主从复制 1 ...

  6. Java:volatile笔记

    Java:volatile笔记 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 1. volatile 和 JMM 内存模型的可见性 JUC 下的三个包 java ...

  7. flink中使用lambda表达式

    flink中使用lambda表达式 1.使用lambda的一个示例 2.使用上面这种写法通常或得到如下错误 3.解决方案 4.建议 5.完整代码 在 java8中有一种新的语法糖,即 lambda表达 ...

  8. nio之缓冲区(Buffer)理解

    一.缓冲区简介 Nio中的 Buffer 是用于存储特定基础类型的一个容器.为了能熟练的使用 Nio中的各种 Buffer , 我们需要理解 Buffer 中的 三个重要 的属性. 1. capaci ...

  9. Noip模拟22 2021.7.21

    T1 d 简化题意就是找到相对平均长宽的偏移量较大的矩形给他删掉 可以说是个贪心,按照a,b分别为第一关键字排序 然后假装删去要求的那么多个按a排序的较小的,然后再去b中, 找到 删去的a中的那几个矩 ...

  10. 字符串与模式匹配算法(五):BMH算法

    一.BMH算法介绍 在BM算法的实际应用中,坏字符偏移函数的应用次数要远远超过好后缀偏移函数的应用次数,坏字符偏移函数在匹配过程中起着移动指针的主导作用.在实际匹配过程,只是用坏字符偏移函数也非常有效 ...