当前位置: 首页 > news >正文

.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。

相关文章:

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

详情请看参考文章&#xff1a;.NET面试题解析(06)-GC与内存管理 - 不灬赖 - 博客园 (cnblogs.com)一、对象创建及生命周期一个对象的生命周期简单概括就是&#xff1a;创建>使用>释放&#xff0c;在.NET中一个对象的生命周期&#xff1a;new创建对象并分配内存对象初始化…...

Java开发 | 内部类 | 静态内部类 | 非静态内部类 | 匿名内部类

目录 1.内部类 1.1内部类的简单创建 1.2内部类的分类 1.2.1普通内部类 1.2.2静态内部类 1.3匿名内部类 1.4局部内部类 1.内部类 内部类就是一是一个类里面装着另外一个类&#xff0c;就像俄罗斯套娃一样。最外层的类我们叫外部类&#xff0c;内层的类我们叫内部类。 1…...

Portal认证

Portal认证Portal认证简介Portal认证协议Portal认证方式Portal认证流程Portal认证用户下线Portal认证简介 定义&#xff1a; Portal认证通常也称作Web认证&#xff0c;一般将Portal认证网站成为门户网站。用户上网时&#xff0c;必须在门户网站进行认证&#xff0c;如果没有认…...

论文解读:ChangeFormer | A TRANSFORMER-BASED SIAMESE NETWORK FOR CHANGE DETECTION

论文地址&#xff1a;https://arxiv.org/pdf/2201.01293.pdf 项目代码&#xff1a;https://github.com/wgcban/ChangeFormer 发表时间&#xff1a;2022 本文提出了一种基于transformer的siamese网络架构&#xff08;ChangeFormer&#xff09;&#xff0c;用于一对共配准遥感图…...

Redis 内存优化技巧

这次跟大家分享一些优化神技如何用更少的内存保存更多的数据&#xff1f;我们应该从 Redis 是如何保存数据的原理展开&#xff0c;分析键值对的存储结构和原理。从而继续延展出每种数据类型底层的数据结构&#xff0c;针对不同场景使用更恰当的数据结构和编码实现更少的内存占用…...

【java】笔试强训Day2【​倒置字符串​与排序子序列】

目录 ⛳选择题 1.A 派生出子类 B &#xff0c; B 派生出子类 C &#xff0c;并且在 java 源代码有如下声明&#xff1a; 2.下面代码将输出什么内容&#xff1a;&#xff08; &#xff09; 3.阅读如下代码。 请问&#xff0c;对语句行 test.hello(). 描述正确的有&…...

【Linux】基础IO(一) :文件描述符,文件流指针,重定向

&#x1f34e;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;Linux系统编程 码字不易&#xff0c;请多多支持&#x1f618;&#x1f618; 这是目录重新认识文件系统内部的文件操作我们C语言的文件操作系统内部的文件操作OS一般会如何让用户给自己传递标志位的&#x…...

【C语言】通讯录的实现(静态版)

【C语言】通讯录的实现(静态版一.前言1.前期准备a.菜单实现b.联系人结构体的构建c.菜单选项的功能d.#define 的定义2.功能的实现a.初始化通讯录b.增加联系人c.显示通讯录d.查找联系人e.修改联系人d.删除联系人3. 总代码test.ccontact.ccontact.h一.前言 本文将会用c语言实现一…...

IDEA一键构建Docker镜像

效果 Idea右击Dockerfile文件&#xff0c;直接在服务器构建docker镜像 开整 1、下载docker插件 2、编写Dockerfile文件 # 基础镜像 FROM openjdk:8-jdk-alpine # 工作目录 WORKDIR /opt/apps/gateway/logs/ # 文件拷贝,把target目录下的jar报拷贝到镜像的/APP/目录下 ADD…...

QT的使用3:鼠标事件

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

线程安全之单例模式

文章目录前言一.什么是单例模式二.在java中的单例模式2.1 饿汉式的介绍2.2 懒汉式的介绍三 懒汉式的单例模式,线程不安全的解决方式3.1 造成线程不安全的原因3.2 解决方案3.3 总结前言 这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计…...

“二分”带来“十分”快感——二分思想的奥秘解析

文章目录无处不在的二分思想二分查找惊人的查找速度二分查找的递归与非递归实现1.循环退出条件2.mid的取值3.low和high的更新最后说一句&#x1f431;‍&#x1f409;作者简介&#xff1a;大家好&#xff0c;我是黑洞晓威&#xff0c;一名大二学生&#xff0c;希望和大家一起进…...

一台服务器最大能支持多少条 TCP 连接?问倒一大片。。。

一台服务器最大能打开的文件数 限制参数 我们知道在Linux中一切皆文件&#xff0c;那么一台服务器最大能打开多少个文件呢&#xff1f;Linux上能打开的最大文件数量受三个参数影响&#xff0c;分别是&#xff1a; fs.file-max &#xff08;系统级别参数&#xff09;&#xf…...

蓝桥杯嵌入式RTC实时时钟

文章目录 前言一、RTC是什么二、cubemx的配置三、函数的使用总结前言 本篇文章将给大家介绍RTC实时时钟。 一、RTC是什么 STM32的实时时钟RTC是一个独立的定时器,RTC时钟内部依靠BCD码计数。RTC实时时钟提高时钟、闹钟、日历功能。RTC功耗较低,可以使用在低功耗设备上。 …...

Centos7 挂载 ISO镜像

切到mnt目录&#xff1a;cd /mnt mkdir iso确保centos镜像在服务上存在,磁盘挂载mount -o loop /home/xx.iso /mnt/iso查看是否挂载成功df -h出现红色的部分表示挂载成功修改源切目录并修改yum源:cd /etc/yum.repos.dllvim Centos-Base.repo修改后yum clean allyum list安装lrz…...

三级数据库备考--数据库应用系统开发方法第一次练习(刷题库知识点记录)

1.数据库的三级模式由外模式、模式、内模式构成。外模式是用户可见的部分数据的存在形式&#xff1b;模式可以等价为全体数据的逻辑结构且用户不可见&#xff0c;是三级模式的中间部分&#xff1b;内模式对应数据库的物理结构和存储方式。当模式改变时&#xff0c;由数据库管理…...

免费空间主机是什么?怎么申请免费空间主机

随着网络的普及&#xff0c;越来越多的人开始使用免费空间。这种新的商业模式也让一些商家得以获利。 1&#xff1a;免费空间的概念 免费空间是指允许您自由使用的网络服务。这意味着它可以被任何人用来创建、编辑和发布网站内容或应用程序&#xff0c;而无需考虑任何付费业务协…...

网络安全文章汇总导航(持续更新)

网络安全文章汇总导航&#xff08;持续更新&#xff09;1.基础篇&#xff08;已完结&#xff09;&#xff1a;2.工具篇&#xff08;持续更新&#xff09;&#xff1a;3.靶场安装&#xff08;持续更新&#xff0c;但不确定&#xff09;&#xff1a;4.权限提升&#xff08;持续更…...

AI-TestOps —— 软件测试工程师的一把利剑

写在前面软件测试的前世今生测试工具开始盛行AI-TestOps 云平台● AI-TestOps 功能模块● AI-TestOps 自动化测试流程写在前面 最近偶然间看到一句话&#xff1a;“软件测试是整个 IT 行业中最差的岗位”。这顿时激起了我对软件测试领域的兴趣&#xff0c;虽然之前未涉及过软件…...

Linux内核进程管理原理详解

前言&#xff1a;Linux内核里大部分都是C语言。建议先看《Linux内核设计与实现(Linux Kernel Development)》,Robert Love&#xff0c;也就是LKD。Linux是一种动态系统&#xff0c;能够适应不断变化的计算需求。Linux计算需求的表现是以进程的通用抽象为中心的。进程可以是短期…...

通过Linux串口实现树莓派与电脑通信

目录 一 串口说明 二 USB—TTL模块 ● usb-ttl模块接口 三 串口通信常用的API 四 修改串口的配置文件 五 串口通信代码验证 ● 发送一个字符/字符串到串口 ● 树莓读取串口数据&#xff08;字符&#xff09; ● 代码拓展&#xff08;双方&#xff09; 一 串口…...

全球变暖 蓝桥杯 178

题目描述你有一张某海域 NxN 像素的照片&#xff0c;"."表示海洋、"#"表示陆地&#xff0c;如下所示&#xff1a;........##.....##........##...####....###........其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座…...

Java现在好找工作吗?

Java到2023年已经28岁了&#xff0c;可能你会怀疑它是否还一如当年一样的强大&#xff0c;在应用层领域独占鳌头。但是基于Java庞大的市场占有率和需求&#xff0c;它依然在保持着更新迭代&#xff0c;依然是最常用的底层开发语言&#xff0c;基于其安全性、开放性、稳定性和跨…...

Flink 第1章 基础介绍和特性

一 Flink概念 1.1 Flink的概念 Flink是一个框架和分布式处理引擎&#xff0c;用于对无界和有解数据流进行状态计算。如下图所示&#xff1a; 1.2 Flink的应用场景 1.3 Flink的目标 1.高吞吐量 2.低延迟 3&#xff0c;结果的准确性和良好的容错性。 1.4 Flink与spark的区别…...

docker 安装 nginx无坑版

一. 拉取镜像 docker pull nginx二. 创建挂载目录 mkdir -p /usr/local/nginx/conf mkdir -p /usr/local/nginx/log mkdir -p /usr/local/nginx/html三. 从nginx容器里复制nginx的配置文件到主机里 创建个容器 docker run --name nginx -p 80:80 -d nginx将容器内的配置文件…...

自己动手做chatGPT:向量的概念和相关操作

chatGPT的横空出世给人工智能注入一针强心剂&#xff0c;它是历史上以最短时间达到一亿用户的应用。chatGPT的能力相当惊人&#xff0c;它可以用相当流利的语言和人对话&#xff0c;同时能够对用户提出的问题给出相当顺畅的答案。它的出现已经给各个行业带来不小冲击&#xff0…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(7)

目录 写在前面&#xff1a; 题目&#xff1a;P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 解题思路&#xff1a; …...

Python嵌套函数(Nested function)和闭包(closure)

Python嵌套函数&#xff08;Nested function&#xff09;和闭包&#xff08;closure&#xff09; 闭包&#xff08;closure&#xff09;是建立在嵌套函数基础上的&#xff0c;是一种特殊的嵌套函数结构。 先看嵌套函数&#xff08;Nested function&#xff09;。 Python允许…...

【实战】React 必会第三方插件 —— Cron 表达式生成器(qnn-react-cron)

文章目录一、引子二、配置使用1.安装2.使用&#xff08;1&#xff09;直接调用&#xff08;2&#xff09;赋值到表单&#xff08;Form&#xff09;&#xff08;3&#xff09;自定义功能按钮&#xff08;4&#xff09;隐藏指定 Tab&#xff08;5&#xff09;其他三、常见问题及解…...

C# 教你如何终止Task线程

我们在多线程中通常使用一个bool IsExit类似的代码来控制是否线程的运行与终止&#xff0c;其实使用CancellationTokenSource来进行控制更为好用&#xff0c;下面我们将介绍CancellationTokenSource相关用法。C# 使用 CancellationTokenSource 终止线程使用CancellationTokenSo…...