.NET/C#/GC与内存管理(含深度解析)

详情请看参考文章:.NET面试题解析(06)-GC与内存管理 - 不灬赖 - 博客园 (cnblogs.com)

一、对象创建及生命周期

一个对象的生命周期简单概括就是:创建>使用>释放,在.NET中一个对象的生命周期:

  • new创建对象并分配内存

  • 对象初始化

  • 对象操作、使用

  • 资源清理(非托管资源)

  • GC垃圾回收

GC的内存管理的目标主要都是引用类型对象,引用对象都是分配在托管堆上的,托管堆中的对象是顺序存放的,托管堆维护着一个指针NextObjPtr,它指向下一个对象在堆中的分配位置。 托管堆的基本结构,如下图:

以下题代码为例,模拟一个对象的创建过程:

public class User
{
    public int Age { get; set; }
    public string Name { get; set; }

    public string _Name = "123" + "abc";
    public List<string> _Names;
}

它的的创建工作原理如下

  • 对象大小估算,共计40个字节

  • 属性Age值类型Int,4字节;

  • 属性Name,引用类型,初始为NULL,4个字节,指向空地址;

  • 字段_Name初始赋值了,代码会被编译器优化为_Name=”123abc”。一个字符两个字节,字符串占用2×6+8(附加成员:4字节TypeHandle地址,4字节同步索引块)=20字节,总共内存大小=字符串对象20字节+_Name指向字符串的内存地址4字节=24字节;

  • 引用类型字段List<string> _Names初始默认为NULL,4个字节;

  • User对象的初始附加成员(4字节TypeHandle地址,4字节同步索引块)8个字节;

  • 内存申请:申请44个字节的内存块,从指针NextObjPtr开始验证,空间是否足够,若不够则触发垃圾回收。

  • 内存分配:从指针NextObjPtr处开始划分44个字节内存块。

  • 对象初始化:首先初始化对象附加成员,再调用User对象的构造函数,对成员初始化,值类型默认初始为0,引用类型默认初始化为NULL;

  • 托管堆指针后移:指针NextObjPtr后移44个字节。

  • 返回内存地址:返回对象的内存地址给引用变量。

二、GC垃圾回收

GC是垃圾回收(Garbage Collect)的缩写,是.NET核心机制的重要部分。她的基本工作原理就是遍历托管堆中的对象,标记哪些被使用对象(那些没人使用的就是所谓的垃圾),然后把可达对象转移到一个连续的地址空间(也叫压缩),其余的所有没用的对象内存被回收掉。

首先,需要再次强调一下托管堆内存的结构,如下图,很明确的表明了,只有GC堆才是GC的管辖区域。GC堆里面为了提高内存管理效率等因素,有分成多个部分,其中 两个主要部分:

  • 0/1/2代:代龄(Generation);

  • 大对象堆(Large Object Heap),大于85000字节的大对象会分配到这个区域,这个区域的主要特点就是:不会轻易被回收;就是回收了也不会被压缩(因为对象太大,移动复制的成本太高了);

什么是垃圾?简单理解就是没有被引用的对象。

垃圾回收的基本流程包含以下三个关键步骤:

① 标记

先假设所有对象都是垃圾,根据应用程序根指针Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。

其中Root根指针保存了当前所有需要使用的对象引用,他其实只是一个统称,意思就是这些对象当前还在使用,主要包含:静态对象/静态字段的引用;线程栈引用(局部变量、方法参数、栈帧);任何引用对象的CPU寄存器;根引用对象中引用的对象;GC Handle table;Freachable队列等。

② 清除

针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。

③ 压缩

把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些Root跟指针的地址修改为移动后的新地址。

垃圾回收的过程示意图如下:

垃圾回收的过程是不是还挺辛苦的,因此建议不要随意手动调用垃圾回收GC.Collect(),GC会选择合适的时机、合适的方式进行内存回收的。

非托管资源回收

.NET中提供释放非托管资源的方式主要是:Finalize() 和 Dispose()。

Dispose():

Dispose需要手动调用,在.NET中有两种调用方式:

//方式1:显示接口调用
SomeType st1=new SomeType();
//do sth
st1.Dispose();

//方式2:using()语法调用,自动执行Dispose接口
using (var st2 = new SomeType())
{
    //do sth
}

第一种方式,显示调用,缺点显而易见,如果程序猿忘了调用接口,则会造成资源得不到释放。或者调用前出现异常,当然这一点可以使用try…finally避免。

一般都建议使用第二种实现方式,他可以保证无论如何Dispose接口都可以得到调用,原理其实很简单,using()的IL代码如下图,因为using只是一种语法形式,本质上还是try…finally的结构。

Finalize() :终结器(析构函数)

首先了解下Finalize方法的来源,她是来自System.Object中受保护的虚方法Finalize,无法被子类显示重写,也无法显示调用,是不是有点怪?。她的作用就是用来释放非托管资源,由GC来执行回收,因此可以保证非托管资源可以被释放。

简单总结一下:Finalize()可以确保非托管资源会被释放,但需要很多额外的工作(比如终结对象特殊管理),而且GC需要执行两次才会真正释放资源。听上去好像缺点很多,她唯一的优点就是不需要显示调用。

有些编程意见或程序猿不建议大家使用Finalize,尽量使用Dispose代替,我觉得可能主要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露。因此就干脆别用了,其实微软是推荐大家使用的,不过是和Dispose一起使用,同时实现IDisposable接口和Finalize(析构函数),其实FCL中很多类库都是这样实现的
这样可以兼具两者的优点:
如果调用了Dispose,则可以忽略对象的终结器,对象一次就回收了;
如果程序猿忘了调用Dispose,则还有一层保障,GC会负责对象资源的释放;

三、性能优化建议

尽量不要手动执行垃圾回收的方法:GC.Collect()

垃圾回收的运行成本较高(涉及到了对象块的移动、遍历找到不再被使用的对象、很多状态变量的设置以及Finalize方法的调用等等),对性能影响也较大,因此我们在编写程序时,应该避免不必要的内存分配,也尽量减少或避免使用GC.Collect()来执行垃圾回收,一般GC会在最适合的时间进行垃圾回收。

而且还需要注意的一点,在执行垃圾回收的时候,所有线程都是要被挂起的(如果回收的时候,代码还在执行,那对象状态就不稳定了,也没办法回收了)。

推荐Dispose代替Finalize

如果你了解GC内存管理以及Finalize的原理,可以同时使用Dispose和Finalize双保险,否则尽量使用Dispose。

选择合适的垃圾回收机制:工作站模式、服务器模式

个人学习总结:

首先了解对象的创建及生命周期

  • new创建对象并分配内存

  • 对象初始化

  • 对象操作、使用

  • 资源清理(非托管资源)

  • GC垃圾回收

其次了解分配到托管堆的基本流程

对象大小估算

内存申请

内存分配

对象初始化

托管堆指针后移

返回内存地址

然后GC的基本工作原理就是遍历托管堆内的所有的引用对象,标记被使用过的对象(也叫可达对象),然后清除不可达对象(清除之后内存变得不再连续),然后把可达对象转移到一个连续的地址空间(也叫压缩)

最后关于GC的一些接口建议:

尽量不要手动执行垃圾回收的方法:GC.Collect()

推荐Dispose代替Finalize

如果你了解GC内存管理以及Finalize的原理,可以同时使用Dispose和Finalize双保险,否则尽量使用Dispose。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】浅谈shell命令以及运行原理

前言&#xff1a;上篇博文把linux下的基本指令讲解完了。本期我们聊聊Linux下【shell】命令及其运行原理。 目录 Shell的基本概念与作用 原理图展示 shell命令执行原理 Shell的基本概念与作用 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;ker…

文心一言 VS ChatGPT,国产大模型和国外的差距有多大?

3月16号&#xff0c;百度正式发布了『文心一言』&#xff0c;这是国内公司第一次发布类ChatGPT的产品。大家一定非常好奇文心一言和chatgpt之间的差距有多大&#xff1f;国产大模型还有多少路可走&#xff1f;本文就全面测评这两款产品&#xff01; 目录 体验网址 1、旅游攻…

【vue2】vue2中的性能优化(持续更新中)

⭐ v-for 遍历避免同时使用 v-if ⭐ v-for 中的key绑定唯一的值 ⭐ v-show与v-if对性能的影响 ⭐ 妙用计算属性 ⭐ 使用防抖与节流控制发送频率 ⭐ 路由守卫处理请求避免重复发送请求 ⭐ 使用第三方UI库的引入方式 【前言】 该系列是博主在使用vue2开发项目中常用上的一…

这些IT行业趋势,将改变2023

上一周&#xff0c;你被"AI"刷屏了吗&#xff1f; 打开任何一家科技媒体&#xff0c;人工智能都是不变的热门话题。周初大家还在用ChatGPT写论文、查资料、写代码&#xff0c;到周末的时候大家已经开始用GPT-4图像识别来做饭、Microsoft 365 Copilot 来写PPT了。 GP…

【Linux】Linux基本指令(下)

前言&#xff1a; 紧接上期【Linux】基本指令&#xff08;上&#xff09;的学习&#xff0c;今天我们继续学习基本指令操作&#xff0c;深入探讨指令的基本知识。 目录 &#xff08;一&#xff09;常用指令 &#x1f449;more指令 &#x1f449;less指令&#xff08;重要&…

【动手学深度学习】(task1)注意力机制剖析

note 将注意力汇聚的输出计算可以作为值的加权平均&#xff0c;选择不同的注意力评分函数会带来不同的注意力汇聚操作。当查询和键是不同长度的矢量时&#xff0c;可以使用可加性注意力评分函数。当它们的长度相同时&#xff0c;使用缩放的“点&#xff0d;积”注意力评分函数…

【问题系列】vue当编辑框被触发就出现保存按钮

目录 问题描述&#xff1a; 解决方案&#xff1a; 1.方案一 2.方案二 3.方案三 问题描述&#xff1a; 一个表单用vue的事件实现当点击编辑按钮(或图标)出现保存按钮&#xff0c;当要编辑的时候只出现编辑按钮&#xff0c;此时保存按钮隐藏 解决方案&#xff1a; 1.方案一…

C++演讲比赛流程管理系统_黑马

任务 学校演讲比赛&#xff0c;12人&#xff0c;两轮&#xff0c;第一轮淘汰赛&#xff0c;第二轮决赛 选手编号 [ 10001 - 10012 ] 分组比赛 每组6人 10个评委 去除最高分 最低分&#xff0c;求平均分 为该轮成绩 每组淘汰后三名&#xff0c;前三名晋级决赛 决赛 前三名胜出 …

KDGX-A光缆故障断点检测仪

一、产品概述 KDGX-A光纤寻障仪是武汉凯迪正大为光纤网络领域施工、测试、维护所设计的一款测试仪表。可实现对光纤链路状态和故障的快速分析&#xff0c;适用于室外维护作业&#xff0c;是现场光纤网络测试与维护中替代OTDR的经济型解决方案。 二、主要特点 1)一键式光纤链路…

基于文心一言的底层视觉理解,百度网盘把「猫」换成了「黄色的猫」

随着移动互联网的一路狂飙&#xff0c;手机已经成为人们的新器官。出门不带钥匙可以&#xff0c;不带手机却是万万不可以的。而手机上&#xff0c;小小的摄像头也越来越成为各位「vlogger」的口袋魔方。每天有超过数亿的照片和视频被上传到百度网盘中&#xff0c;这些照片和视频…

【机器学习算法复现】随机森林,以又放回的方式构建的决策树为基础的集成学习方法,可回归可分类不同任务注意评价指标。

随机森林就是通过集成学习的Bagging思想将多棵树集成的一种算法&#xff1a;它的基本单元就是决策树。随机森林的名称中有两个关键词&#xff0c;一个是“随机”&#xff0c;一个就是“森林”。“森林”很好理解&#xff0c;一棵叫做树&#xff0c;那么成百上千棵就可以叫做森林…

CSS 扫盲

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录引入方式内部样式内联样式外部样式CSS 选择器CSS 常用属性值字体属性设置字体大小粗细文字样式文本属性文本颜色文本对齐文本装…

Docker基础篇——最全讲解

文章目录一、CentOS安装docker二、启动帮助类命令三、镜像命令1.名词概念2.常用命令2.1 镜像命令2.2 容器命令2.2.1&#xff1a;常用参数2.2.2&#xff1a;常用指令2.3 安装单机mysql、redis一、CentOS安装docker docker官网 1&#xff09;yum安装gcc相关&#xff1a; yum -y…

【Spring从成神到升仙系列 五】从根上剖析 Spring 循环依赖

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…

经典七大比较排序算法 ·上

经典七大比较排序算法 上1 选择排序1.1 算法思想1.2 代码实现1.3 选择排序特性2 冒泡排序2.1 算法思想2.2 代码实现2.3 冒泡排序特性3 堆排序3.1 堆排序特性&#xff1a;4 快速排序4.1 算法思想4.2 代码实现4.3 快速排序特性5 归并排序5.1 算法思想5.2 代码实现5.3 归并排序特性…

QT的使用3:鼠标事件

鼠标事件0 事件1 需求2 查看控件的事件处理函数3 UI设计4 新建一个类&#xff0c;继承QLabel5 对已有对象进行类型提升6 重写事件处理函数7 项目进一步拓展&#xff08;1&#xff09;获取鼠标按键&#xff08;2&#xff09;鼠标移动&#xff08;3&#xff09;显示多个按键&…

【数据结构】Java实现栈

目录 1. 概念 2. 栈的使用 3. 自己动手实现栈&#xff08;使用动态数组实现栈&#xff09; 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…

计算机网络笔记——物理层

计算机网络笔记——物理层2. 物理层2.1 通信基础2.1.1 信号2.1.2 信源、信道及信宿2.1.3 速率、波特及码元2.1.4 带宽2.1.5 奈奎斯特定理采样定理奈奎斯特定理2.1.6 香农定理2.1.7 编码与调制调制数字信号调制为模拟信号模拟数据调制为模拟信号编码数字数据编码为数字信号模拟数…

C#中WPF实现依赖注入和MVVM,以及服务定位ServiceLocator

最近在想重写架构于是就研究了一套WPF的相关内容&#xff0c;WPF不像MAUI内置了容器&#xff0c;需要我们自己手动添加&#xff0c;于是就有了今天的内容。 首先&#xff0c;我们新建一个.net6.0的WPF项目 由于WPF没有内置容器,我们先安装一下依赖注入的nuget包 Microsoft.Ex…

网络技术与应用概论(上)——“计算机网络”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容依旧是计算机网络的一些知识点噢&#xff0c;下面&#xff0c;让我们进入计算机网络的世界吧 网络内涵 网络特征 网络定义 互联网发展过程 从ARPA网络到Internet 从低速互联网到高速互联网 从数据结构到统一网…