垃圾回收 (GC) 在 .NET Core 中是如何工作的?

news2024/11/17 7:55:14

        提起GC大家肯定不陌生,但是让大家是说一下GC是怎么运行的,可能大多数人都不太清楚,这也很正常,因为GC这东西在.NET基本不用开发者关注,它是依靠程序自动判断来释放托管堆的,我们基本不需要主动调用Collect()释放内存,只需要注意对非托管资源进行及时释放就行。

        虽说我们不用关注GC的运行,但是作为一个合格的程序员,还是有必要知道她是怎么工作的,因为垃圾回收对于一个程序来说真的太重要了,下面我们就用实际的应用来看下GC在.NET Core下是怎么工作的:

GC 会分配堆段,其中每个段都是一系列连续的内存。 置于堆中的对象归类为 3 个代系之一:0、1 或 2。 代系可确定 GC 尝试在应用不再引用的托管对象上释放内存的频率。 编号较低的代系会更加频繁地进行 GC。

对象会基于其生存期从一个代系移到另一个代系。 随着对象生存期延长,它们会移到较高代系。 如前所述,较高代系进行 GC 的频率较低。 短期生存的对象始终保留在第 0 代中。 例如,在 Web 请求存在期间引用的对象的生存期较短。 应用程序级别单一实例通常会迁移到第 2 代。

当 ASP.NET Core 应用启动时,GC 会:

  • 为初始堆段保留一些内存。
  • 在运行时加载时提交一小部分内存。

进行以上内存分配是出于性能方面的原因。 性能优势来自连续内存中的堆段。

GitHub 上提供了 MemoryLeak 示例应用。 MemoryLeak 应用:

运行起来是这样的

  • Allocated:托管对象占用的内存量(当前系统认为要分配内存量)
  • Working set:进程的虚拟地址空间中当前驻留在物理内存中的页集。 显示的工作集与任务管理器显示的值相同。(为当前进程分配的物理内存量)
  • Gen 0:表示第0代堆段被回收
  • Gen 1:表示第1代堆段被回收(同时回收0、1代)
  • Gen 2:表示第2代堆段被回收(同时回收0、1、2代)
  • RPS:每秒请求数

GC = Server  asp.net 默认的是服务端GC

示例提供了很多接口用于调用测试

暂时性对象

我们先来看一下第一个接口:

[HttpGet("bigstring")]
public ActionResult<string> GetBigString()
{
    return new String('x', 10 * 1024);
}

创建一个 10-KB 字符串实例,并将它返回给客户端。 对于每个请求,会在内存中分配一个新对象并将它写入响应中。 字符串作为 UTF-16 字符存储在 .NET 中,因此每个字符都需要 2 字节内存。

使用压力测试工具Apache JMeter - Apache JMeter™

对这个接口进行压力测试,来观察内存使用情况

运行压力测试工具后可以看到内存使用及GC运行情况:

可以看到RPS在3K左右,当内存使用量升到了100M左右GC进行了第0代垃圾回收,第 0 代 GC 回收大约每两秒进行一次,内存消耗和释放(通过 GC)是稳定的,很少出现第1代回收,是因为分配的内存都是临时的小内存,并发量也在程序的可处理范围内,基本在第0代就可以完全回收。

持久性对象引用

接下来看下一个接口:

private static ConcurrentBag<string> _staticStrings = new ConcurrentBag<string>();
        [HttpGet("staticstring")]
        public ActionResult<string> GetStaticString()
        {
            var bigString = new String('x', 10 * 1024);
            _staticStrings.Add(bigString);
            return bigString;
        }

GC 无法释放上面的静态资源对象。 引用了不再需要的对象会导致内存泄露。 如果应用经常分配对象,但在不再需要对象之后未能释放它们,则内存使用量会随着时间推移而增加。

上面的 API 创建一个 10-KB 字符串实例,并将它返回给客户端。 与上一个示例的不同之处在于,此实例由静态成员引用,这意味着它不能被GC回收。

运行压力测试工具后可以看到内存使用及GC运行情况:

在上图中:

  • /api/staticstring进行压力测试,会导致内存线性增加。
  • GC 会在内存压力增加时,通过调用Collect来尝试释放内存,但是基本无济于事。
  • GC 无法释放泄漏的内存。 已分配内存和工作集会随时间而增加。

停止压力测试后内存还在持续占用,手动调用Collect()也无济于事

我们只能调用Clear()来清理静态资源,然后Collect()进行回收

[HttpGet("clear")]
        public ActionResult<string> Clear()
        {
            _staticStrings.Clear();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            return "";
        }

本机内存

来看下一个接口:

[HttpGet("fileprovider")]
public void GetFileProvider()
{
    var fp = new PhysicalFileProvider(TempPath);
    fp.Watch("*.*");
}

某些 .NET Core 对象依赖于本机内存。 GC 无法回收本机内存。 使用本机内存的 .NET 对象必须使用本机代码进行释放。

.NET 提供了 IDisposable 接口,使开发人员能够释放本机内存。 即使未调用 Dispose,正确实现的类也会在终结器运行时调用 Dispose

PhysicalFileProvider 是托管类,因此任何实例在请求结束时都会被回收。

运行压力测试工具后可以看到内存使用及GC运行情况:

上面的图表显示此类的实现存在一个明显问题,它会不断增加内存使用量,这是因为忘记调用应释放的相关对象的 Dispose 方法

我改一下这个接口,使用using调用Dispose :

private static readonly string TempPath = Path.GetTempPath();

        [HttpGet("fileprovider")]
        public void GetFileProvider()
        {
            using (var fp = new PhysicalFileProvider(TempPath))
            {
                fp.Watch("*.*");
            }               
        }

可以看到内存得到了稳定的释放,GC调用也相对稳定

大型对象堆

频繁的内存分配/释放周期可能会导致内存碎片,尤其是在分配大型内存区块时。 对象在连续内存块中进行分配。 为了减少碎片,当 GC 释放内存时,它会尝试对其进行碎片整理。 此过程称为压缩。 压缩涉及移动对象。 移动大型对象会造成性能损失。 因此,GC 会为大型对象创建特殊内存区域,称为大型对象堆 (LOH)。 大于 85,000 字节(大约 83 KB)的对象:

  • 置于 LOH 上。
  • 不进行压缩。
  • 在第 2 代 GC 期间进行回收。

当 LOH 已满时,GC 会触发第 2 代回收。 第 2 代回收:

  • 在本质上速度较慢。
  • 还会产生对所有其他代系触发回收的成本。

下面的代码会立即压缩 LOH:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

在使用 .NET Core 3.0 及更高版本的容器中,LOH 会自动压缩。

我们来看下一个api:

[HttpGet("loh/{size=85000}")]
public int GetLOH1(int size)
{
   return new byte[size].Length;
}

用压力测试工具调用 /api/loh/84975 这个接口

换一下对象大小 调用 /api/loh/84976 这个接口

比较上面两个图表

  • 工作集对于这两种方案是相似的(大约 450 MB)。
  • 低于 LOH 请求(84,975 字节)大部分显示第 0 代回收。
  • 高于 LOH 请求(84,976 字节)生成恒定的第 2 代回收。 第 2 代回收成本高昂。 需要更多 CPU

 84,976 字节会就触发了 85,000 限制

所以临时大型对象有性能问题,因为它们会导致第 2 代 GC。

为了获得最佳性能,应最大程度减少大型对象使用。 如果可能,请拆分大型对象。 例如,ASP.NET Core 中的响应缓存中间件会将缓存项拆分为小于 85,000 字节的块。

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

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

相关文章

山峰个数 - 华为OD统一考试

OD统一考试 分值: 100分 题解: Java / Python / C++ 题目描述 给定一个数组,数组中的每个元素代表该位置的海拔高度。0表示平地,>=1时表示属于某个山峰,山峰的定义为当某个位置的左右海拔均小于自己的海拔时,该位置为山峰。数组起始位置计算时可只满足一边的条件。 …

原子学习笔记1——阻塞和非阻塞IO

阻塞式 I/O 顾名思义就是对文件的 I/O 操作&#xff08;读写操作&#xff09;是阻塞式的&#xff0c;非阻塞式 I/O 同理就是对文件的I/O 操作是非阻塞的。 当对文件进行读操作时&#xff0c;如果数据未准备好、文件当前无数据可读&#xff0c;那么读操作可能会使调用者阻塞&…

Linux---cp和mv命令选项

1. cp命令选项 命令选项说明-i交互式提示-r递归拷贝目录及其内容-v显示拷贝后的路径描述-a保留文件的原有权限 cp -i命令选项效果图: cp -r命令选项效果图: cp -v命令选项效果图: cp -a命令选项效果图: -a选项说明: -a 选项还支持拷贝文件夹并且文件夹中的文件权限不丢失 …

WPF-附加属性《十二》

非常重要 依赖属性和附加属性&#xff0c;两者是有关系的&#xff0c;也是有些区别的&#xff0c;很多时候&#xff0c;可能会把两者混淆了。 附加属性&#xff08;Attach Property&#xff09; 顾名思义&#xff0c;就是附加上面的属性&#xff0c;自身是没有的&#xff0c;…

详解Keras3.0 KerasCV API: StableDiffusion image-generation model

Stable Diffusion 图像生成模型&#xff0c;可用于根据简短的文本描述&#xff08;称为“提示”&#xff09;生成图片 keras_cv.models.StableDiffusion(img_height512, img_width512, jit_compileTrue) 参数说明 img_height&#xff1a;int&#xff0c;要生成的图像的高度…

TCP/IP详解——UDP 协议

文章目录 1. UDP1.1 UDP 头部1.2 UDP 校验和1.3 UDP 传输过程1.4 UDP-Lite1.5 最大 UDP 数据报长度1.6 UDP 输入队列 1. UDP UDP&#xff1a;用户数据报协议&#xff08;User Datagram Protocol&#xff09;面向无连接的&#xff0c;也就是无需建立连接&#xff0c;传输不可靠。…

PDF转为图片

PDF转为图片 背景pdf展示目标效果 发展过程最终解决方案&#xff1a;python PDF转图片pdf2image注意&#xff1a;poppler 安装 背景 最近接了一项目&#xff0c;主要的需求就是本地的文联单位&#xff0c;需要做一个电子刊物阅览的网站&#xff0c;将民族的刊物发布到网站上供…

音视频参数介绍

一、视频参数概念 单个视频帧&#xff1a;可以简单地理解成为一张图片 单个视频帧主要的参数概念&#xff1a; 分辨率&#xff1a; 分辨率是指图像或显示器上像素的数量&#xff0c;通常用横向像素数乘以纵向像素数表示。例如&#xff0c;1920x1080 表示宽度为1920像素&…

数据结构 | Log-Structured Merge Tree (LSM Tree)

今天介绍LSM Tree这个数据结构&#xff0c;严格意义上来说&#xff0c;他并不像他的名字一样是一棵树型的数据结构&#xff0c;而更多是一种设计思想。 LSM Tree最先在1996年被提出&#xff0c;后来被广泛运用于现代NoSQL&#xff08;非关系型数据库&#xff09;系统中&#xf…

【Spark精讲】Spark与MapReduce对比

目录 对比总结 MapReduce流程 ​编辑 MapTask流程 ReduceTask流程 MapReduce原理 阶段划分 Map shuffle Partition Collector Sort Spill Merge Reduce shuffle Copy Merge Sort 对比总结 Map端读取文件&#xff1a;都是需要通过split概念来进行逻辑切片&…

CSS新手入门笔记整理:CSS常用属性表

字体样式 属性 属性值 说明 font-family 微软雅黑/苹方 字体类型 font-size 数值 字体大小 font-weight 数值/关键字 字体粗细&#xff08;字重&#xff09; font-style normal&#xff08;正常(默认值)&#xff09;italic&#xff08;斜体&#xff09;oblique&am…

湖南财信:灰盒测试筑牢安全防线,保障差异化金融服务体系建设

湖南财信是国有独资公司&#xff0c;湖南省唯一的省级地方金融控股公司、省属国有大型骨干企业。湖南财信坚持金融科技战略&#xff0c;以数字化建设为驱动&#xff0c;创新融资服务手段&#xff0c;逐步打造出差异化产品与数字化服务体系&#xff0c;全方位、多维度推动金融服…

大语言模型:开启自然语言处理新纪元

导言 大语言模型&#xff0c;如GPT-3&#xff08;Generative Pre-trained Transformer 3&#xff09;&#xff0c;标志着自然语言处理领域取得的一项重大突破。本文将深入研究大语言模型的基本原理、应用领域以及对未来的影响。 1. 简介 大语言模型是基于深度学习和变压器&…

WPF 基于TableControl的页面切换

文章目录 前言其它项目的UserControl切换TableControl添加按钮&#xff0c;隐去TableItem的Header 结论 前言 我想用WPF简单实现一个按钮视图切换的效果&#xff0c;但是我发现别人的实现效果非常的麻烦。 其它项目的UserControl切换 我网上找了个开源的项目&#xff0c;他是…

网络协议 - HTTP 协议详解

网络协议 - HTTP 协议详解 一 、基础概念URL请求和响应报文1. 请求报文2. 响应报文 二、HTTP 方法GETHEADPOSTPUTPATCHDELETEOPTIONSCONNECTTRACE 三、HTTP 状态码1XX 信息2XX 成功3XX 重定向4XX 客户端错误5XX 服务器错误 四、HTTP 首部通用首部字段请求首部字段响应首部字段实…

如何提升数据结构方面的算法能力?

谈及为什么需要花时间学算法&#xff0c;我至少可以列举出三个很好的理由。 (1)性能&#xff1a;选择正确的算法可以显著提升应用程序的速度。仅就搜索来说&#xff0c;用二分查找替 换线性搜索就能为我们帶来巨大的收益。 (2)安全性&#xff1a;如果你选用了错误的算法&…

软件供应链投毒 — NPM 恶意组件分析

聚焦源代码安全&#xff0c;网罗国内外最新资讯&#xff01; 专栏供应链安全 数字化时代&#xff0c;软件无处不在。软件如同社会中的“虚拟人”&#xff0c;已经成为支撑社会正常运转的最基本元素之一&#xff0c;软件的安全性问题也正在成为当今社会的根本性、基础性问题。 随…

最新Redis7主从复制(保姆级教程)

前提准备&#xff1a;三台云服务器&#xff08;吐血消费&#xff0c;点赞回血&#xff09;也可以使用虚拟机创建三台&#xff0c;但是我搞了一天也连接不上&#xff0c;要是又可以连接上的大家可以教我一下&#xff0c;也可以参考一下或者大家可以参考一下这个大佬的配置&#…

【产品经理】产品增效项目落地,项目反哺产品成长

产品和项目是相辅相成的关系&#xff0c;产品的规范、成熟&#xff0c;为项目的快速落地提供支撑&#xff0c;项目的落地反哺产品&#xff0c;促进产品的成长成熟。 软件工程的初期是&#xff0c;我们需要什么&#xff0c;就立项项目&#xff0c;通过项目实现需要。 随着项目的…

hive的分区表和分桶表详解

分区表 Hive中的分区就是把一张大表的数据按照业务需要分散的存储到多个目录&#xff0c;每个目录就称为该表的一个分区。在查询时通过where子句中的表达式选择查询所需要的分区&#xff0c;这样的查询效率会提高很多。 静态分区表基本语法 创建分区表 create table dept_p…