从ASP.NET Core 3.0 preview 特性,了解CLR的Garbage Collection
前言在阅读这篇文章:Announcing Net Core 3 Preview3的时候,我看到了这样一个特性:
大概的意思呢就是在 .NET Core 3.0 版本中,我们已经通过修改 GC 堆内存的最大值,来避免这样一个情况:在 docker 容器中运行的 .NET Core 程序,因为 docker 容器内存限制而被 docker 杀死。 恰好,我在 docker swarm 集群中跑的一个程序,总是被 docker 杀死,大都是因为内存超出了限制。那么升级到 .NET Core 3.0 是不是会起作用呢?这篇文章将浅显的了解 .NET Core 3.0 的 GCCLR.NET 程序是运行在 CLR : Common Language Runtime 之上。CLR 就像 JAVA 中的 JVM 虚拟机。CLR 包括了 JIT 编译器,GC 垃圾回收器,CIL CLI 语言标准。 那么 .NET Core 呢?它运行在
那么什么时候对象会被分配到堆内存中呢? 所有引用类型的对象,以及作为类属性的值类型对象,都会分配在堆中。大于 85000byte 的对象扔到 “大象房” 里。 堆内存中的对象越少,GC 干的事情越少,你的程序就越快,因为 GC 在干事的时候,程序中的其他线程都必须毕恭毕敬的站着不动(挂起),等 GC 说:我已经清理好了。然后大家才开始继续忙碌。所以 GC 一直都是在干帮线程擦屁股的事情。 所以没有 GC 的编程语言更快,但是也更容易产生废物。 GC Generation那么 GC 在收拾垃圾的过程中到底做了什么呢?首先要了解 CLR 的 GC 有一个
零代和一代 占用的内存因为他们都是短暂对象,所以叫做短暂内存块。 那么他们占用的内存大小是多大?32位和63位的系统是不一样的,不同的GC类型也是不一样的。 WorkStation GC: 32 位操作系统 16MB ,64位 操作系统 256M Server GC: 32 w位操作系统 65MB,64 位操作系统 4GB! GC 回收过程当 管理堆内存中使用到达一定的阈值的时候,这个阈值是GC 决定的,或者系统内存不够用的时候,或者调用 GC 会在 Generation 0 中开始巡查,如果是 死对象,就把他们的内存释放,如果是 活的对象,那么就标记这些对象。接着把这些活的对象升级到下一代:移动到下一代 Generation 1 中。 同理 在 Generation 1 中也是如此,释放死对象,升级活对象。 三个 Generation 中,Generation 0 被 GC 清理的最频繁,Generation 1 其次,Generation 2 被 GC 访问的最少。因为要清理 Generation 2 的消耗太大了。 GC 在每一个 Generation 进行清理都要进行三个步骤:
WorkStation GC 和 Server GCGC 有两种形式: 默认的.NET 程序都是 WorkStation GC ,那么 WorkStation GC 和 Server GC 有什么区别呢。 上面已经提到一个区别,那就是 Server GC 的 Generation 内存更大,64位操作系统 Generation 0 的大小居然有4G ,这意味着啥?在不调用 还有一个很大的区别就是,Server GC 拥有专门用来处理 GC的线程,而WorkStation GC 的处理线程就是你的应用程序线程。WorkStation 形式下,GC 开始,所有应用程序线程挂起,GC选择最后一个应用程序线程用来跑GC,直到GC 完成。所有线程恢复。 而ServerGC 形式下: 有几核 CPU ,那么就有几个专有的线程来处理 GC。每个线程都一个堆进行GC ,不同的堆的对象可以相互引用。 所以在GC 的过程中,Server GC 比 WorkStation GC 更快。但是有专有线程,并不代表可以并行GC 哦。 上面两个区别,决定了 Server GC 用于对付高吞吐量的程序,而WorkStation GC 用于一般的客户端程序足以。 如果你的.NET 程序正在疲于应付 高并发,不妨开启 Server GC : https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcserver-element Concurrent GC 和 Non-Concurrent GCGC 有两种模式: Concurrent GC 当然是为了解决上述 GC 过程中所有线程挂起等待 GC 完成的问题。因为工作线程挂起将会影响 用户交互的流畅性和响应速度。 Concurrent 并行实际上 只发生在Generation 2 中,因为 Generation 0 和 Generation1 的处理是在太快了,相当于工作线程没有阻塞。 在 GC 处理 Generation 2 中的第一步,也就是标记过程中,工作线程是可以同步进行的,工作线程仍然可以在 Generation 0 和 Generation 1 中分配对象。 所以并行 GC 可以减少工作进程因为GC 需要挂起的时间。但是与此同时,在标记的过程中工作进程也可以继续分配对象,所以GC占用的内存可能更多。 而Non-Concurrent GC 就更好理解了。 .NET 默认开启了 Concurrent 模式,可以在 https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcconcurrent-element 进行配置 Background GC又来了一种新的 GC 模式: 首先:Background GC 和 Concurrent GC 都是为了减少 因为 GC 而挂起工作线程的时间,从而提升用户交互体验,程序响应速度。 其次:Background GC 和 Concurrent GC 一样,都是使用一个专有的GC 线程,并且都是在 Generation 2 中起作用。 最后:Background GC 是 Concurrent GC 的增强版,在.NET 4.0 之前都是默认使用 Concurrent GC 而 .NET 4.0+ 之后使用Background GC 代替了 Concurrent GC。 那么 Background GC 比 Concurrent GC 多了什么呢: 之前说到 Concurrent GC 在 Generation 2 中进行清理时,工作线程仍然可以在 Generation 0/1 中进行分配对象,但是这是有限制的,当 Generation 0/1 中的内存片段 Segment 用完的时候,就不能再分配了,知道 Concurrent GC 完成。而 Background GC 没有这个限制,为啥呢?因为 Background GC 在 Generation 2 中进行清理时,允许了 Generation 0/1 进行清理,也就说是当 Generation 0/1 的 Segment 用完的时候, GC 可以去清理它们,这个GC 称作 所以 Background GC 比 Concurrent GC 减少了更多 工作线程暂停的时间。 GC 的简单概念就到这里了以上是阅读大量英文资料的精短总结,如果有写错的地方还请斧正。 作为最后一句总结GC的话:并不是使用了 Background GC 和 Concurrent GC 的程序运行速度就快,它们只是提升了用户交互的速度。因为 专有的GC 线程会对CPU 造成拖累,此外GC 的同时,工作线程分配对象 和正常的时候分配对象 是不一样的,它会对性能造成拖累。 .NET Core 3.0 的变化
实际体验从开头的 介绍 ASP.NET Core 3.0 文章中了解到 ,在 Docker 中,对容器的资源限制是通过 cgroup 实现的。cgroup 是 Linux 内核特性,它可以限制 进程组的 资源占用。当容器使用的内存超出docker的限制,docker 就会将改容器杀死。在之前 .NET Core 版本中,经常出现 .NET Core 应用程序消耗内存超过了docker 的 内存限制,从而导致被杀死。而在.NET Core 3.0 中这个问题被解决了。 为此我做了一个实验。 这是一段代码: using System; using System.Collections.Generic; using System.Threading; namespace ConsoleApp1 { class Program { static void Main(string[] args) { if (GCSettings.IsServerGC == true) Console.WriteLine("Server GC"); else Console.WriteLine("GC WorkStationGC"); byte[] buffer; for (int i = 0; i <= 100; i++) { buffer = new byte[ 1024 * 1024]; Console.WriteLine($"allocate number {i+1} objet "); var num = GC.CollectionCount(0); var usedMemory = GC.GetTotalMemory(false) /1024 /1024; Console.WriteLine($"heap use {usedMemory} mb"); Console.WriteLine($"GC occurs {num} times"); Thread.Sleep(TimeSpan.FromSeconds(5)); } } } } 这段代码是在 for 循环 分配对象。 .NET Core 2.2 运行的结果是: GC WorkStationGC allocate number 1 objet heap use 1 mb GC occurs 0 times allocate number 2 objet heap use 2 mb GC occurs 0 times allocate number 3 objet heap use 3 mb GC occurs 0 times allocate number 4 objet heap use 1 mb GC occurs 1 times allocate number 5 objet heap use 2 mb GC occurs 1 times allocate number 6 objet heap use 3 mb GC occurs 1 times allocate number 7 objet heap use 4 mb GC occurs 2 times allocate number 8 objet heap use 5 mb GC occurs 3 times allocate number 9 objet heap use 6 mb GC occurs 4 times allocate number 10 objet heap use 7 mb GC occurs 5 times allocate number 11 objet heap use 8 mb GC occurs 6 times allocate number 12 objet heap use 9 mb Exit 首先.NET Core 2.2默认使用 WorkStation GC ,当heap使用内存到达9mb时,程序就被docker 杀死了。 在.NET Core 3.0 中 GC WorkStationGC allocate number 1 objet heap use 1 mb GC occurs 0 times allocate number 2 objet heap use 2 mb GC occurs 0 times allocate number 3 objet heap use 3 mb GC occurs 0 times allocate number 4 objet heap use 1 mb GC occurs 1 times allocate number 5 objet heap use 2 mb GC occurs 1 times allocate number 6 objet heap use 3 mb GC occurs 1 times allocate number 7 objet heap use 1 mb GC occurs 2 times allocate number 8 objet heap use 2 mb GC occurs 2 times allocate number 9 objet heap use 3 mb GC occurs 2 times .... 运行一直正常没问题。 二者的区别就是 .NET Core 2.2 GC 之后,堆内存没有减少。为什么会发生这样的现象呢? 一下是我的推测,没有具体跟踪GC的运行情况 我也试过将分配的对象大小设置小于 85kb,.NET Core 3.0 和.NET Core2.2 在内存限制小于10mb都可以正常运行,这应该是和 GC 在 Generation 0 中的频繁清理的机制有关,因为清理几乎不消耗时间,不像 Generation 2,所以在没有限制GC heap的情况也可以运行。 我将上述代码 发布到了 StackOverFlow 和Github 进行提问, https://stackoverflow.com/questions/56578084/why-doesnt-heap-memory-used-go-down-after-a-gc-in-clr https://github.com/dotnet/coreclr/issues/25148 有兴趣可以探讨一下。 总结.NET Core 3.0 的改动还是很大滴,以及应该根据自己具体的应用场景去配置GC ,让GC 发挥最好的作用,充分利用Microsoft 给我们的权限。比如启用Server GC 对于高吞吐量的程序有帮助,比如禁用 Concurrent GC 实际上对一个高密度计算的程序是有性能提升的。 参考文章
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net-mvc – 使用ASP.net MVC项目作为其他MVC项目的“基
- asp.net – 无法加载程序集“App_Web_kh7-x3ka”.确保在访问
- asp.net-mvc – MVC 3 RC上的全局过滤器注册错误
- asp.net-mvc – MVC – 强类型视图被破坏
- 如何在ASP.NET Web应用程序(而不是MVC)中使用Razor语法
- ASP.NET Core 2.0 使用支付宝PC网站支付实现代码
- asp.net-mvc-3 – 如何在View中使用ViewBag
- 自己动手写ORM框架
- asp.net – 显示为普通html的.cshtml文件
- asp.net 无刷新分页实例代码