从CLR GC到CoreCLR GC看.NET Core对云原生的支持

内存分配概要

前段时间在园子里看到有人提到了 GC 学习的重要性,很赞同他的观点。充分了解 GC 可以帮助我们更好的认识 .NET 的设计以及为何在云原生开发中 .NET Core 会占有更大的优势,这也是一个程序员成长到更高层次所需要经历的过程。在认识 GC 的过程中,我们先看一下.NET中内存分配的概要知识。
.NET 分配内存,主要依据托管资源和非托管资源进行分配。托管资源分配到了托管堆中并受 CLR 的管理,非托管资源分配到了非托管堆中。该节主要讨论托管资源的分配。
CLR 支持两种基本类型:值类型和引用类型。CLR 对这两种类型在运行时有两种分配方式:
在这里插入图片描述
内存的分配过程如下图所示:
在这里插入图片描述
需要注意的是,CLR 还要维护一个指针,称为 NextObjPtr,这个指针指向下一个对象再堆中的分配位置。初始化时,NextObjPtr 设为地址空间区域的基地址。一个区域被非垃圾对象填满后,CLR 会分配更多的区域,指针也会不断偏移。new 操作符会返回对象的引用,就在返回这个引用之前,NextObjPtr 指针的值会加上对象占用的字节数来得到一个新值,即下一个对象放入托管堆时的地址。

垃圾回收算法与GC运行机制

常用的垃圾回收算法主要有 引用计数算法引用跟踪算法。引用计数有着明显的缺陷,.NET 使用的垃圾回收算法是引用跟踪法。小记:关于垃圾回收算法,我记得有一个知识点,在C#中如果出现了循环引用是否会导致内存溢出?如果比较了解这两种算法就会知道不会溢出。

GC Root

引用跟踪算法,通过一系列 GCRoot 对象作为起始点,从这些点开始向下搜索,搜索的路径成为引用链,当一个对象到GC没有任何引用链,说明对象可以被回收。GC Root可以类比树来解释:
在这里插入图片描述
GC 根节点存在于堆栈中,指向Teacher引用对象。它包含一个ArrayList订单集合,由Teacher对象引用。集合本身也包含对其元素的引用,随着搜索深度的增加,树也不断长大。GC根节点的引用源来自:

  • (1) 堆栈
  • (2) 全局或静态变量
  • (3) CPU寄存器
  • (4) 互操作引用(COM / API调用中使用的.NET对象)
  • (5) 对象终结引用(objects finalization references)

补充GC引用跟踪原理:引用跟踪算法只关心 引用类型变量,因为只有它才能引用堆上的对象。引用类型的变量可以出现在字段、方法参数、局部变量 等场合中。这些所有引用类型的 变量 都称之为 “根(root)”。GC 从遇到的第一个根开始,将根引用的对象进行标记,同时也标记这个被引用的对象中包含的根所引用的对象,如此遍历完所有可达的根之后,就找出了所有正在使用中的对象。不可达的对象就可以销毁了。

GC运行机制

GC引入了代的概念,分为三种代(即,GC将托管堆分成了三个区域):G0、G1、G2。

一个新产生的对象会被首先放在G0中(大对象除外),若G0空间被占满,则触发一次 GC,从而清理G0区域,并把存活的对象移动到G1中,同样地,G1满之后将被GC清理并把存活的对象移动到G2中。

G0对象生存周期较短,越往后生存周期越长(虽然G2中由于直接存储了大对象,又由于G2不是每次都会扫描,所以大多数情况下,G2中的对象的生存周期比G0中的更长)。GC运行机制如下图所示:

在这里插入图片描述
需要注意的是,CLR 想要进行垃圾回收时,会立即挂起执行托管代码中的所有线程,正在执行非托管代码的线程不会挂起。所以再多线程环境下,可能会出现莫名其妙的诡异问题。
下图为 GC 的整体运行流程,包含五个步骤:

在这里插入图片描述

垃圾回收时机与模式

CLR 会在一下情况发生时,执行 GC 操作:
(1)当 GC 的代的预算大小已经达到阈值而无法对新对象分配空间的时候,比如 GC 的第0代已满;
(2)显式调用System.GC.Collect()(显示调用要慎重,因为手动调用可能会与自动执行的GC冲突,从而导致无法预知的问题);
(3)其他特殊情况,比如,操作系统内存不足、CLR 卸载 AppDomain、CLR 关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收。

关于GC模式主要有:

  • WorkStation GC
  • Server GC
  • Concurrent GC
  • Non-Concurrent GC
  • Background GC

详细信息请参阅:https://www.cnblogs.com/dacc123/p/10980718.html,这篇文章关于GC模式的说明比较详细。

.NET Core 3.0 中的 GC 优化处理

.NET Core 3.0 默认更好的支持 Docker 资源限制,官方团队也在努力让 .NET Core 成为真正的容器运行时,使其在低内存环境中具有容器感知功能并高效运行。

GC堆限制

.NET Core 减少了 CoreCLR 默认使用的内存,如 G0 代内存分配预算,以更好地与现代处理器缓存大小和缓存层次结构保持一致。

在新的创建的 GC 堆数量的策略里,GC保留了一个内存片段,每个堆最小是16M,在低内存限制的机器上也可以很好的运行。在多核CPU的机器上运行时,系统并没有设置CPU的核数限制。例如,如果在48核计算机上设置160 MB内存限制,则不需要创建48个GC堆。也就是说如果设置160 MB限制,则只会创建10个GC堆。如果未设置CPU限制,应用程序可以利用计算机上的所有核心。

有了这样的新策略,可以不需要启用 Docker 环境下的 .NET Core 应用的工作站 GC 的工作负载。

支持 Docker 内存限制

Docker 资源限制建立在 cgroup 之上,而 cgroup 是 Linux 的内核功能。从运行时的角度来看,我们需要定位 cgroup 原语。

设置cgroup限制时的 .NET Core 3.0 内存使用规则:

  • 默认GC堆大小:容器上cgroup内存限制的最大值 20MB 或最大值的 75%
  • 每个GC堆的最小保留段大小16MB,这将减少在具有大量内核和小内存限制的计算机上创建的堆数

为了支持容器方案,添加了2个 HardLimit 配置:

  • GCHeapHardLimit - 指定GC堆的硬限制
  • GCHeapHardLimitPercent - 指定允许此进程使用的物理内存的百分比

如果同时指定了两者,则首先检查 GCHeapHardLimit,并且只有在未指定 GCHeapHardLimit时才检查GCHeapHardLimitPercent

如果两者都未指定,但进程正在有内存限制的容器中运行,则默认是使用如下设置:

  • max(20mb,容器内存限制的75%)

如果指定了hardlimit配置,并且程序在有内存限制的容器中使用,GC 堆的使用不会超过hardlimit限制,但总内存仍然受容器的内存限制。所以当我们统计内存消耗时,基于容器内存限制得出的数据。

举例:
进程在设置了200MB限制的容器中运行,用户还将GCHeapHardLimit配置为100MB。
如果把GC限制中100MB限制中的50MB用于GC,而容器限制中剩余的100MB用于其他用途,那么内存消耗即为(50+100)/200=75%。

GC 将更积极地执行资源回收与释放,因为 GC 堆越接近 GCHeapHardLimit 限制,就越能实现提供更多可用内存的目标,也越能使得应用程序可以继续而又安全地运行。如果算法计算出的结果认为此时的 GC 效率低下,那么将避免持续执行完全阻塞的 GC。

即使GC堆完全压缩,GC 依然会抛出一个 OutOfMemoryException 异常出来,这是因为所分配的堆大小超过了 GCHeapHardLimit 的限制。

由此可见,.NET Core 3.0 的设计是要稳定运行于有资源限制的容器中。

支持 DockerCPU 限制

在CPU限制的情况下,Docker上设置的值将向上舍入为下一个整数值。此值是 CoreCLR 使用的最大有效CPU核数。

默认情况下,ASP.NET Core 应用程序启用了服务器 GC(它不适用于控制台应用程序),因为它可以实现高吞吐量并减少跨核心的争用。当进程仅限于单个处理器时,运行时会自动切换到工作站GC。即使您明确指定使用服务器GC,工作站GC也将始终用于单核环境。

通过计算CPU繁忙时间,设置CPU限制,我们避免了线程池的各种推导性竞争:

  • 尝试分配更多的线程以增加CPU繁忙时间
  • 尝试分配更少的线程,因为添加更多的线程不会提高吞吐量

参考文献

[1] Using .NET and Docker Together – DockerCon 2019 Update
[2] Proposal for .NET Core GC Support for Docker Limits
[3] 从ASP.NET Core 3.0 preview 特性,了解CLR的Garbage Collection
[4] .NET下的内存分配机制

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值