CLR via C#(三)垃圾回收

news2024/11/18 16:45:18

一、资源生命周期

每个程序运行都需要各种资源,如文件、内存缓冲区、数据库等。要使用这些资源,就必须为代表资源的类型分配内存。访问一个资源所需的步骤如下:

  • 调用IL指令newobj,为代表资源的类型分配内存(在C#中一般用new操作符完成)
  • 初始化内存,设置资源的初始状态并使资源可用
  • 访问类型成员来使用资源
  • 摧毁资源状态以进行清理
  • 释放内存(这一步由垃圾回收器负责)

二、从托管堆分配资源

CLR要求所有对象都从托管堆分配。进程初始化时,CLR划分出一个地址空间区域作为托管堆。同时CLR还维护着一个指针,这里把它称作NextObjPtr。该指针指向下一个对象在堆中的分配位置。初始时NextObjPtr设置为地址空间区域的基地址。

当我们使用new操作符实例化对象时,CLR会检查区域中是否有足够的空间分配对象。如果空间足够,就在NextObjPtr指向的地址放入对象,并将这块区域清零。然后调用类型构造器,并为this参数传递NextObjPtrnew操作符返回对象的引用。在返回这个引用之前,NextObjPtr的值会加上这个对象占用的字节数,从而指向下一个可分配空间的地址。
image.png

由于托管堆在内存中连续分配这些对象,所以会因为引用的“局部化”而获得性能上的提升。因为在差不多时间分配的对象彼此间有较强的联系,很可能也会在差不多的时间访问。对于这类对象,如果在内存上的位置是连续的,那么进程的工作集就会很小,应用程序只需要使用很少的内存,从而提高了速度。同时,这还意味着代码使用的对象可以全部驻留在CPU缓存中。因而在CPU执行大多数操作时,不会因为“缓存未命中”而被迫访问较慢的RAM。

三、垃圾回收

3.1 垃圾回收算法

对于对象生存期的管理,有些系统采用的是引用计数算法。在这种系统中,堆上的每个对象都维护着一个内存字段来统计程序中有多少地方正在使用对象。随着每个地方不再需要对象,就会递减对象的计数字段。当计数字段变为0时,对象就可以从内存中删除了。

但引用计数最大的问题就是无法处理循环引用的问题。当对象a引用对象b,对象b又引用对象a时,两个对象的计数器就永远无法达到0,对象也就永远不会被删除。

为了避免这种问题,CLR改为使用一种引用跟踪算法。引用跟踪算法只关心引用类型的变量,我们将所有的引用类型变量都称为

CLR开始GC时,首先暂停进程中的所有线程。这样可以防止线程在CLR检查期间访问对象并更改其状态。然后CLR进入GC的标记阶段。在这个阶段,CLR遍历堆上的所有对象,将同步块索引字段中的一位设置为0,表明所有的对象都需要删除。然后CLR检查所有活动根,查看它们引用了哪些对象。如果一个根包含null,则忽略这个根并继续检查下个根。

任何根只要引用了堆上的对象,CLR都会标记那个对象,也就是将该对象的同步块索引中的位设置为1。一个对象被标记后,CLR会检查那个对象中的根,并标记它们引用的对象。如果发现对象已经标记,就不重新检查对象的字段。这样就避免了循环引用而产生的死循环。
image.png

检查完毕后,堆中的对象要么就已标记,要么就未标记。已标记的对象不能被垃圾回收,因为还有引用存在。这种对象称为可达的。未标记的对象是不可达的。

此时CLR就已经知道了哪些对象可以幸存,哪些可以直接删除。接下来就进入GC的压缩阶段。在这个阶段,CLR将所有幸存的对象进行移动,使其在内存中紧挨在一起。这一操作一方面恢复了引用的“局部化”,从而提升了性能;另一方面也使剩余空间变得连续,解决了空间碎片化的问题。

因为幸存对象所在位置都被移动了,所以CLR还要从每个根减去所引用对象在内存中偏移的字节数。这样就保证了每个根引用的还是之前的对象。
image.png

3.2 代

CLR的GC是基于代的垃圾回收器,它假设你的代码符合以下条件:

  • 对象越新,生存期越短
  • 对象越老,生存期越长
  • 回收堆的一部分,比回收整个堆快
    对于大多数的应用程序,这些假设都是成立的。

现在假设托管堆刚刚初始化完成,我们向其中添加了一些新构造的对象,此时这些对象都属于第0代。此时垃圾回收器还从未检查过它们。一段时间后,其中的某些对象变得不可达(深色表示可达,浅色表示不可达)
image.png

在CLR初始化时会为第0代设置一个预算容量,如果分配一个新对象使第0代超出了预算,就会启动一次垃圾回收,存活的对象会成为第1代。
image.png

CLR在初始化时同样会为第1代设置一个预算容量。当开始一次垃圾回收时,垃圾回收器会检查第1代占用了多少内存。如果第1代占用的内存小于预算容量,那么就只需要检查第0代中的对象。由于第0代对象存活的时间更短,因而第0代包含更多垃圾的可能性也更大,所以也可以回收更多的内存。

忽略第1代中的对象可以提升垃圾回收器的性能。在进行垃圾回收时不必遍历托管堆中的每个对象。如果根或对象引用了老一代的某个对象,垃圾回收器可以忽略老对象内部的所有引用,从而加快构建“可达对象图”。老对象中的字段也有可能引用新的对象,为了确保对老对象的已更新字段进行检查,JIT编译器内部会在对象的引用字段发生变化时设置一个对应的标志位。这样垃圾回收器就知道从上次垃圾回收以来,哪些老对象已经被写入。只有字段发生变化的老对象才需要检查是否引用了第0代中的新对象。

当第0代达到了容量预算,第1代也达到了容量预算,那么在本次垃圾回收中,垃圾回收器就会检查第0代和第1代中所有的对象,并将幸存的对象都提升1代
image.png
image.png

托管堆最多只支持三代:第0代、第1代、第2代。CLR初始化时,会为每一代都设置一个预算。但这个预算是会动态变化的,它取决于应用程序的行为。比如垃圾回收器发现回收第0代后存活下来的对象很少,就会减少第0代的预算。这意味着垃圾回收会更频繁地发生,但每次回收所做的事情也会减少。

3.3 垃圾回收触发条件

  • CLR检测到第0代超出容量预算时
  • 代码显式调用System.GC.Collect()方法
  • Windows报告低内存情况
  • CLR正在卸载AppDomain
  • CLR正在关闭

3.4 大对象

CLR将对象分为大对象和小对象。目前认为85000字节或更大的对象是大对象。
大对象具有以下特点:

  • 大对象在独立的进程地址空间进行分配
  • GC不压缩大对象,因为在内存中移动它们的代价过高。但这也会导致大对象之间的地址空间碎片化。
  • 大对象总是第2代,绝不可能是第0代或第1代。所以只能为需要长时间存活的资源创建大对象。

3.5 垃圾回收模式

GC有两种基本的模式:

  • 工作站模式。该模式针对客户端应用优化GC。GC造成的延时很低,应用程序线程挂起的时间很短。该模式GC假定机器上运行的其他应用不会消耗太多CPU资源。
  • 服务器模式。该模式针对服务器应用优化GC。该模式GC假定机器上没有运行其他应用,所有的CPU都可以用来辅助完成GC。该模式会造成托管堆被拆分成几个区域,每个CPU负责一个区域。垃圾回收开始时,垃圾回收器在每个CPU上运行一个特殊线程,它们并发回收自己的区域。

除了上述两种模式外,GC还支持两种子模式:并发和非并发。在并发方式中,垃圾回收器有个额外的后台线程,能在应用运行时并发标记对象。一个线程因为分配对象造成第0代超出预算时,GC首先挂起所有线程,再判断要回收哪些代。如果要回收第0代或第1代,则仍然用非并发方式进行回收。

但如果要回收第2代,就会增大第0代的大小,以便在第0代中分配新对象,然后应用程序的线程恢复运行。此时垃圾回收器运行一个普通优先级的后台线程来查找不可达对象。找到之后,垃圾回收器再次挂起所有线程,判断是否需要压缩内存。如果执行压缩,所需的时间也会比平常少,因为不可达对象集合已经由后台线程构造好了。但如果可用内存多,垃圾回收器可能会选择不压缩。

四、终结器

GC可以回收对象在托管堆中的内存,但如果对象包含本机资源(如文件、网络连接等),直接回收对象就会造成本机资源的泄露。CLR为这种情形提供了终结机制,允许对象在被回收前执行一些代码,来释放持有的本机资源。

System.Object类中定义了虚方法Finalize。垃圾回收器判定对象是垃圾后,就会调用对象的Finalize方法。C#使用在类名前添加~符号来定义Finalize方法。

public class MyClass
{
	~MyClass()
	{
		// ...
	}
}

如果对象的类型定义了Finalize方法,那么在该类型的实例构造器被调用之前,会将指向该对象的指针放到一个终结队列中。
image.png

垃圾回收器扫描终结队列以查找这些对象的引用。找到一个引用后,该引用会从终结队列中移除,并添加到freachable队列。这个队列代表Finalize方法已准备好调用的对象。
image.png
CLR使用一个特殊的、高优先级的专用线程调用Finalize方法来避免死锁。当freachable队列为空时,该线程将会休眠,一旦有记录项就会被唤醒。该线程将freachable队列中的每一项移除,并调用其Finalize方法。

当一个对象不可达时,垃圾回收器将其视为垃圾。但当对象的引用被移至freachable队列时,对象不再被认为是垃圾,不能回收其内存(包括其引用的对象)。也就是说对象被复活了。被复活的对象会提升到较老的一代,直到下一次对老一代进行垃圾回收时,已终结的对象才会成为真正的垃圾并被回收。也就是说,可终结对象需要执行至少两次垃圾回收才能释放它们占用的内存

五、参考资料

[1].《CLR via C#(第四版)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018160.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NSS [HNCTF 2022 WEEK2]ohmywordpress(CVE-2022-0760)

NSS [HNCTF 2022 WEEK2]ohmywordpress(CVE-2022-0760) 题目描述:flag在数据库里面。 开题: 顺着按钮一直点下去会发现出现一个按钮叫安装WordPress 安装完之后的界面,有一个搜索框。 F12看看network。 又出现了这个…

在 Android 设备或仿真器上进行测试

🎬 岸边的风:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 Windows Defender 概述 如何将排除项添加到 Windows Defender Android 开发时要考虑的排除项 本指南介绍如何在 W…

JavaScript逆向循环和嵌套循环

逆向循环 ● 我们还是使用以下这个数组进行演示 const ITshareArray ["张三","二愣子",2033 - 1997,"程序员",["李四", "王五", "牛二"], ];● 现在我们还是想循环的列出数组的值,但是我们倒着来&a…

l14 IO模型

一、基本概念 I/O即数据的读取&#xff08;接收&#xff09;或写入&#xff08;发送&#xff09;操作 通常用户进程中的一个完整I/O分为两个阶段 1.用户进程空间<-->内核空间 2.内核空间<-->设备空间&#xff08;磁盘、网卡等&#xff09; I/O分为内存I/O、网络…

Bash脚本学习 - 条件句、数组、for循环,函数

1. 条件测试 [ 和 ] 是一个用于执行条件测试的命令。它们必须用空格分隔开&#xff0c;并且在 [ 后面和 ] 前面必须有空格。-eq 是一个比较运算符&#xff0c;表示等于&#xff08;equal&#xff09;。它用于比较两个值是否相等。 2. 条件句 在 ifelseifelse.sh 文件中&#…

Acwing 827. 双链表

Acwing 827. 双链表 题目要求思路讲解初始化在第k个点右边插入&#xff1a;如果想在k的左边插入x&#xff0c;只要这样调用就可以了&#xff1a;删除怎么做&#xff1a; 代码展示 题目要求 思路讲解 初始化 在第k个点右边插入&#xff1a; 如果想在k的左边插入x&#xff0c;只…

【算法训练-二叉树 一】【遍历二叉树】前序遍历、中序遍历、后续遍历、层序遍历、锯齿形层序遍历、二叉树右视图

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【二叉树的遍历】&#xff0c;使用【二叉树】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为…

如何实现一个IO口读取多个设备信息

前言 &#xff08;1&#xff09;今天遇到一个有意思的问题一个IO口如何读取多个电机的堵转问题。之后他就发了一张图片 &#xff08;2&#xff09;看到这个问题&#xff0c;之前先说一个简单的。我们如何实现一个IO读取多个按键&#xff0c;了解了这个之后&#xff0c;对于多个…

Python 变量

视频版教程 Python3零基础7天入门实战视频教程 变量 无论使用什么语言编程&#xff0c;总要处理数据&#xff0c;处理数据就需要使用变量来保存数据。变量就像一个个小容器&#xff0c;用于“盛装”程序中的数据。 再说说&#xff0c;Python的数据类型&#xff0c;有以下六种…

Prometheus+Grafana 基础监控告警体系搭建(一):服务的基本安装

文章目录 1. prometheus 安装1.1. 下载安装包1.2. 基本设置1.3. 解压安装包1.4. 配置 service 启动文件1.5. 启动服务 2. Grafana 安装2.1. 下载安装包2.2. 安装2.3. 启动服务 3. 总结一下 打算重新练下手&#xff0c;免得忘记了&#xff0c;这次内网进行了全面部署演示&#x…

编程任务|随机摆放的刀叉开始认识数学思维

任务源自旧版的Brilliant数学讨论问题。2019-09-02我曾经发布过&#xff0c;可惜已经下线&#xff0c;幸活大喵做足备份。 该问题看似是概率问题&#xff0c;实则不然。 官方给出的解法透露出一个非常重要的数学思维方法&#xff1a; 数学语言 —— 为何以及如何构造一个函数 f…

计算机丢失msvcr110.dll解决办法,多种msvcr110.dll解决方法分享

随着 Windows 操作系统的发展&#xff0c;越来越多的用户在使用电脑时遇到了计算机丢失 msvcr110.dll 的问题。msvcr110.dll 是 Windows 操作系统中的一个动态链接库文件&#xff0c;它包含了许多常用的 C 运行库函数。因此&#xff0c;当计算机丢失 msvcr110.dll 时&#xff0…

Kotlin File useLines nameWithoutExtension extension

Kotlin File useLines nameWithoutExtension extension import java.io.Filefun main(args: Array<String>) {val filePath "myfile.txt"val file File(filePath)println(file.name) //文件名字&#xff0c;不包括路径println(file.isFile) //是文件吗pri…

PTA:7-4 顺序表(删除)

顺序表&#xff08;删除&#xff09; 题目输入样例输出样例 代码 题目 输入样例 10 55 11 9 15 67 12 18 33 6 22 10 20输出样例 55 9 67 33 6 22代码 #include<iostream> using namespace std; void deletes(int* arr, int& sz, int left, int right) {int newId…

Vue入门简介(带你打开Vue的大门)

目录 前言 一、Vue简介 1. 什么是Vue 2. Vue的应用场景 3. Vue的作用&#xff08;重要性&#xff09; 4. 什么是MVVM模式 5. 开源库网址 二、Vue入门使用 1. 基础使用步骤 1.1 引入Vue.js 1.2 创建Vue实例 1.3 编写Vue模板 1.4 数据绑定与指令 1.5 调用Vue方法和…

【C++】哈希位图和布隆过滤器

哈希位图优缺点位图应用模拟实现代码 哈希布隆过滤器哈希布隆过滤器的提出哈希布隆过滤器概念模拟实现代码 为什么哈希布隆图要比位图省空间 哈希位图和布隆过滤器都是常用的概率数据结构&#xff0c;用于高效地判断一个元素是否存在于一个集合当中&#xff0c;但它们在实现方法…

The driver has not received any packets from the server

在测试数据迁移时遇到的错误。 目录 一、错误 二、解决 三、数据迁移测试 3.1 环境 3.2 源码及测试 3.2.1 源码 3.2.2 测试结果&#xff08;太慢&#xff09; 3.2.3 源码修改 3.2.4 异常及解决 一、错误 The driver has not received any packets from the server. 二…

关于软件测试的方法详解,你知道多少呢?

一、 软件测试方法 1. 软件测试方法包括&#xff1a;白盒测试(White Box Testing)、黑盒测试(Black Box Testing)、灰盒测试、静态测试、动态测试。 2. 白盒测试&#xff1a;是一种测试用例设计方法&#xff0c;在这里盒子指的是被测试的软件&#xff0c;白盒&#xff0c;顾名…

想要精通算法和SQL的成长之路 - 最长回文子序列

想要精通算法和SQL的成长之路 - 最长回文子序列 前言一. 最长回文子序列 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最长回文子序列 原题链接 首先&#xff0c;我们看下动态规划方程的定义&#xff0c;我们用dp[i][j] 来代表&#xff1a;字符串s在下标区间为[i,j]之间…

Java“牵手”lazada商品列表页数据采集+lazada商品价格数据排序,lazadaAPI接口申请指南

Lazada是东南亚首屈一指的网上购物平台&#xff0c;中文名为来赞达&#xff0c;拥有较多的品牌和销售商。2016年&#xff0c;Lazada成为阿里巴巴集团的区域旗舰&#xff0c;并得到了阿里巴巴一流的技术基础设施的支持。 Lazada来赞达在2012年成立&#xff0c;总部设在新加坡&a…