简介

CLR的GC模式与JVM的GC模式理念不同,相对JVM的各种小参显得比较简陋,CLR的理念是约定优于配置,并根据程序类型来分提供了几个默认的选项给大家选择。

  1. CS程序默认使用的工作站模式(WorkStation Mode)
  2. BS程序默认使用的服务器模式(Server Mode)



不同的模式,堆的默认大小也有不同。

但在.NET 8 之后,64位程序不管使用什么模式,都采用region管理法, 4M为一个segment。

https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

WorkStation Mode

工作站模式主要是为了满足基于UI的程序所需的响应性而设计的,因此意味着GC的停顿要尽可能的短。

为了实现GC少停顿,工作站模式有如下几个特征:

  1. 只有一个托管堆

    因为系统不可能只运行一个程序,每一个程序都会占用cpu与内存。因此多个堆反而会降低GC处理效率。从一开始工作站模式就被设定为只能由一个线程处理一个托管堆

  2. segment更小,GC触发频率更高

    高频次的GC,会让GC的处理时间变短,因为对象变少了。GC需要做的准备工作也变少。用小步快跑的方式均衡因 gc 而暂停程序的时间

眼见为实



在WorkStation模式下,托管堆只有一个,且段空间很小(256M)

Server Mode

服务器模式是为了能满足处理并发请求的程序而设计的,这意味着它更看重吞吐量而不是GC停顿

为了平衡吞吐量与GC停顿,服务器模式有如下几个特征:

  1. 堆的数量与CPU 核心数保持一致

    大多数情况下,为了保证吞吐量,服务器只会部署一个程序,因此该程序基本上能“独享”整个CPU与内存。此时多个堆的并行处理能力比单个大堆处理更快。
  2. segment更大,GC频率相对降低

眼见为实



在 Server Mode下,托管堆数量与CPU保持一致,且段空间较大(1GB)

眼见为实:.NET 8 之后,统一采用region管理法, 4M为一个segment。



并发模式与非并发模式

从线程维度来看,GC还分为发模式与非并发模式。

  1. 非并发模式

    顾名思义,不能并发。在GC期间,所有托管线程都被暂停,待GC执行完后,再恢复线程的执行

  2. 并发模式

    顾名思义,支持并发。在GC期间,线程在特定情况下不会被暂停,从而提高吞吐量。

因此CLR提供的总共是2x2=4 ,4种GC模式供人选择。

JVM调优与CLR调优的差别

在JVM的世界中,JVM以GC为中心,提供了非常细颗粒度的配置让用户来自主选择,自由度非常高,但也需要非常了解参数背后的意义,上限很高的同时,下限也很低。

在CLR的世界中,CLR以Application为中心,提供4种模式让用户来自主选择,自由度相对较低,但提高了程序的下限。

举个例子: JVM是SSM,CLR是Spring Boot/Spring Cloud。 将开发人员从配置地狱中拉出来

由于C#支持值类型,因此优化方向主要在代码层面,而不是在CLR层面

CLR调优

虽然说约定大于配置,但也不是完全不能改。

CLR提供了少量参数,能让你调整堆数量,堆大小,是否启用并发GC等。

相对JVM来说,还是偏少。但是对比Framework,已经有了非常显著的进步。

https://docs.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector

CLR调优的重点是使用栈分配来降低堆分配,而不是调整堆配置。

JVM认为一切皆对象,因此绝大多数对象都分配在堆上,所以需要调整堆配置,来达到GC的平衡点。

非并发工作站模式

作为最简单的一种GC模式,前文已经介绍过,不再赘述。

  1. GC发生在用户线程上,没有额外的GC线程
  2. 所有线程都被挂起

眼见为实:GC操作在调用它的线程上





4号线程触发了GC,GC操作直接在4号线程上运行。

眼见为实:其它线程也被暂停



可以看到,6号线程被“劫持”,进入了暂停状态。

202a0:通常表示线程正在运行或可运行状态。

2b220:可能表示线程处于等待状态,比如等待某个资源或事件。

非并发GC服务器模式

  1. 相对WorkStation,最大的不同就是有专门的GC线程来处理,GC线程数与heap数一致,大多数情况下,它们被挂起以等待GC工作。
  2. 所有回收都是非并发GC,多个GC线程并行回收,相对非并发工作站模式。STW的暂停时间要短得多。
  3. 标记阶段也是多个GC线程并行完成,所以标记阶段的阻塞耗时也相对较短。


//用户线程触发GC
GarbageCollectGeneration()
{
//wake up an event
ee_suspend_event.set();
wait_for_gc_done();
} //gc线程:轮询处理
gc_thread_function()
{
while (1)
{
ee_suspend_event.Wait(); SuspendEE();
garbage_collect();
RestartEE();
}
} garbage_collect()
{
generation_to_condemn();
gc1();
} gc1()
{
mark_phase();
plan_phase();
} plan_phase()
{
// actual plan phase work to decide to
// compact or not
if (compact)
{
relocate_phase();
compact_phase();
}
else
make_free_lists();
}

眼见为实:GC线程数与Heap数一致



16核CPU,会创建16个Heap,从而创建16个GC 线程

眼见为实:GC发生在GC线程上

后台GC工作站模式

相对非并发工作站模式,后台GC使用一个单独的线程

前文讲到,如果GC决定压缩回收,因为要移动对象,所以这个时候STW是无法避免的,但如果GC决定标记清除,那就不一样了,完全可以做到托管线程不暂停或者少暂停来减少 STW 的时间,这就是后台GC要解决的问题。

简单来说,当FullGC决定标记清除时,后台GC可以让托管线程的绝大多数时间都处于可运行状态

因为临时回收速度很快,使用并发模式反而性能不高。因此只有在FullGC下才使用并发GC

//线程触发GC
GarbageCollectGeneration()
{
SuspendEE();
garbage_collect();
RestartEE();
} //CLR 发现当前是fullgc 而且是标记清除,觉得有必要走bgc逻辑,接下来会生成 bgc 线程
garbage_collect()
{
generation_to_condemn();
// decide to do a background GC
// wake up the background GC thread to do the work
do_background_gc();
} //初始化 bgc 线程,通过 ee_proceed_event事件 激活 bgc 线程。
do_background_gc()
{
init_background_gc();
start_c_gc (); //wait until restarted by the BGC.
wait_to_proceed();
} //bgc线程执行 gc1 函数,开始做 标记阶段 和 清扫阶段。
bgc_thread_function()
{
while (1)
{
// wait on an event
// wake up
gc1();
}
} gc1()
{
//此时的mark_phase会进入二阶段暂停,而不是暂停所有线程
background_mark_phase();
//bgc在后台做标记清除,此时的托管线程在忙自己的活,处于一种并发状态
background_sweep();
}

如何不干扰正常线程的同时,标记对象?

在正常情况下(STW),标记一个对象,可以利用MethodTable来写入额外信息,但在并发情况下,线程正在使用时,修改MethodTabl是非常危险的,因此并发标记将有关的信息存储在专用的标记数组中,由于GC是该数组的唯一写入/访问者,因此不会出现同步问题。

如何保持root根的一致性?mark_phase二阶段暂停



在并发模式下,之前被标记过的内存,也可能发生变化。此时需要重新标记发生变化的内存来确保无误。此时需要借助WriteWatch机制的来实现重新标记

WriteWatch类似卡表/卡表,只修改单个对象也会导致整个内存页失效。

mark_phase二阶段暂停大致分为如下几个步骤

  1. 线程挂起期间,仅遍历线程栈与终结器的根,遍历完成后进入初始标记阶段
  2. 通过restart_vm()恢复所有托管线程的运行状态,bgc线程继续提取根对象(句柄表),进入并发标记阶段
  3. 再次冻结线程,通过WriteWatch,又回过头重新扫描被修改的内存,进入最终标记阶段
  4. 再次恢复托管线程,bgc在后台完成Free标记

眼见为实:初始标记阶段,GC线程处于SuspendEE

点击查看代码
    internal class Program
{
static void Main(string[] args)
{
Debugger.Break();
Alloc();
Console.ReadLine();
} static List<string> list = new List<string>();
static Random rand = new Random(); static void Alloc()
{
for (int i = 0; i < int.MaxValue; i++)
{
list.Add(string.Join(",", Enumerable.Range(1, 1000))); if (i > 100)
{
list[rand.Next(0, i)] = null;
}
}
}
}

使用bp coreclr!WKS::gc_heap::background_mark_phase 下断点

主线程触发GC,触发了SuspendEE

眼见为实:并发标记阶段,托管线程处于正常状态

使用bp coreclr!WKS::gc_heap::revisit_written_pages 下断点

主线程正常运行,移除了触发线程SuspendEE的标记

眼见为实:最终标记阶段,托管线程处于SuspendEE

使用bp coreclr!WKS::gc_heap::background_sweep 下断点

GC线程又触发了SuspendEE,实现最终标记。



其它线程被暂停

眼见为实:清扫阶段,又将托管线程解冻

使用bp coreclr!WKS::gc_heap::compute_new_dynamic_data 下断点

SuspendEE标记又被清除



其它线程恢复正常运行

后台GC服务器模式

此GC模式是最复杂的一种,但有了前三种GC模式的铺垫,相信你已经摸清了它的套路。

与后台工作站模式类似,但不同的是

  1. 每个托管堆有两个专门用于GC的线程

    1.1 一个是服务器GC线程,负责执行堆中阻塞式GC,对应gc_thread_function函数

    1.2 一个是后台GC线程,负责执行堆中后台式GC,对应bgc_thread_function函数

0,1代等临时回收使用阻塞式GC,因为临时回收速度足够快

FULLGC根据实际情况采用后台GC或者阻塞式GC


GarbageCollectGeneration()
{
//wake up an event
ee_suspend_event.set();
wait_for_gc_done();
} gc_thread_function()
{
while (1)
{
ee_suspend_event.Wait(); SuspendEE();
garbage_collect();
RestartEE();
}
} garbage_collect()
{
generation_to_condemn();
// decide to do a background GC
// wake up the background GC thread to do the work
do_background_gc();
} do_background_gc()
{
init_background_gc();
start_c_gc(); //wait until restarted by the BGC.
wait_to_proceed();
} bgc_thread_function()
{
while (1)
{
bgc_start_event.Wait(); gc1();
}
} gc1()
{
background_mark_phase();
background_sweep();
}

眼见为实

LowLatencyGC

除了以上4种GC模式外,还可以代码来设置延迟模式,来控制GC导致的阻塞频次。

    //
// 摘要:
// Adjusts the time that the garbage collector intrudes in your application.
public enum GCLatencyMode
{
//
// 摘要:
// Disables garbage collection concurrency and reclaims objects in a batch call.
// This is the most intrusive mode. This mode is designed for maximum throughput
// at the expense of responsiveness.
//该模式下,垃圾回收器以批量方式工作,尽可能提高吞吐量。
//在这种模式下,垃圾回收器会等待更多的内存被分配后才进行回收操作,以减少垃圾回收的频率,从而提高应用程序的整体吞吐量。不过,这可能会导致较长的垃圾回收停顿时间
//适用于对响应时间要求不高,但对吞吐量有较高要求的应用程序,例如批量数据处理任务、长时间运行的后台作业等。这些应用可以容忍较长的垃圾回收停顿,因为它们主要关注的是在单位时间内处理更多的数据。
Batch = 0,
//
// 摘要:
// Enables garbage collection concurrency and reclaims objects while the application
// is running. This is the default mode for garbage collection on a workstation
// and is less intrusive than System.Runtime.GCLatencyMode.Batch. It balances responsiveness
// with throughput. This mode is equivalent to garbage collection on a workstation
// that is concurrent.
//这是默认的垃圾回收模式。它在吞吐量和响应时间之间进行了平衡,允许垃圾回收器在应用程序运行过程中进行并发的垃圾回收操作,以减少应用程序的停顿时间。
//在这种模式下,垃圾回收器会更频繁地进行小规模的回收操作,从而保持应用程序的响应性。
//适用于大多数交互式应用程序,如桌面应用程序、Web 应用程序等。这些应用需要及时响应用户的操作,因此不能容忍过长的垃圾回收停顿时间。
Interactive = 1,
//
// 摘要:
// Enables garbage collection that is more conservative in reclaiming objects. Full
// collections occur only if the system is under memory pressure, whereas generation
// 0 and generation 1 collections might occur more frequently. This mode is not
// available for the server garbage collector.
//该模式着重于减少垃圾回收的停顿时间,以提供低延迟的响应。垃圾回收器会更频繁地进行小规模的回收操作,并且会尽量避免长时间的 “STW” 停顿。
//不过,这种模式可能会降低应用程序的吞吐量,因为垃圾回收操作会更频繁地打断应用程序的执行。
//适用于对延迟非常敏感的应用程序,如实时游戏、金融交易系统等。这些应用需要在短时间内对用户输入或外部事件做出响应,因此对垃圾回收的停顿时间有严格的要求
LowLatency = 2,
//
// 摘要:
// Enables garbage collection that tries to minimize latency over an extended period.
// The collector tries to perform only generation 0, generation 1, and concurrent
// generation 2 collections. Full blocking collections may still occur if the system
// is under memory pressure.
//此模式旨在提供持续的低延迟性能,尤其适用于需要长时间保持低延迟的应用场景。与 LowLatency 模式相比,SustainedLowLatency 模式会更加保守地进行垃圾回收,尽量避免触发可能导致长时间停顿的大型垃圾回收操作。
//不过,这也可能会导致内存使用量逐渐增加,因为垃圾回收器不会及时回收所有的垃圾对象。
//用于需要长时间保持低延迟的应用程序,如高频交易系统、实时数据分析系统等。这些应用在长时间运行过程中都需要快速响应,不能容忍明显的垃圾回收停顿。
SustainedLowLatency = 3,
//
// 摘要:
// Indicates that garbage collection is suspended while the app is executing a critical
// path. System.Runtime.GCLatencyMode.NoGCRegion is a read-only value; that is,
// you cannot assign the System.Runtime.GCLatencyMode.NoGCRegion value to the System.Runtime.GCSettings.LatencyMode
// property. You specify the no GC region latency mode by calling the Overload:System.GC.TryStartNoGCRegion
// method and terminate it by calling the System.GC.EndNoGCRegion method.
// 迄今为止可以设置的最强p配置,只要内存足够,该模式会在代码执行期间尝试禁止垃圾回收,
NoGCRegion = 4
}

眼见为实

举个例子1:

    static void Main()
{
// 设置垃圾回收模式为 LowLatency
System.GC.LatencyMode = System.Runtime.GCLatencyMode.LowLatency; // 模拟一些工作
for (int i = 0; i < 1000000; i++)
{
// 创建一些对象
var obj = new byte[1024];
Thread.Sleep(1);
} // 恢复默认的垃圾回收模式
System.GC.LatencyMode = System.Runtime.GCLatencyMode.Interactive;
}

举个例子2:

using System;
using System.Runtime; class Program
{
static void Main()
{
// 创建一个无垃圾回收区域
using (NoGCRegion noGC = new NoGCRegion(NoGCRegionOptions.BestEffort))
{
// 在这个代码块内,尽量避免进行完整的垃圾回收
// 执行对延迟敏感的操作,例如处理高频交易订单
for (int i = 0; i < 1000; i++)
{
// 模拟一些对延迟敏感的工作
Console.WriteLine($"Processing item {i}");
}
}
// 离开无垃圾回收区域后,垃圾回收器恢复正常工作
}
}

https://www.cnblogs.com/cdaniu/p/15927791.html

https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.gclatencymode?view=net-9.0

总结

GC 模式 目标场景 特点 适用场景
Workstation GC 客户端应用 低延迟,单线程或并发 桌面应用、UI 应用
Server GC 服务器应用 高吞吐量,多线程 ASP.NET、Web API 等服务器应用
Background GC 低延迟 + 高吞吐量 允许后台回收,减少停顿 对延迟敏感的服务器或客户端应用
SustainedLowLatency 极低延迟 避免完全垃圾回收,减少停顿 实时系统、游戏、金融交易系统

https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/garbage-collection.md

.NET Core GC模式(gc mode)底层原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  2. CSRF漏洞原理浅谈

    CSRF漏洞原理浅谈 By : Mirror王宇阳 E-mail : mirrorwangyuyang@gmail.com 笔者并未深挖过CSRF,内容居多是参考<Web安全深度剖析>.& ...

  3. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  4. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

  5. CAS+SSO原理浅谈

    http://www.cnblogs.com/yonsin/archive/2009/08/29/1556423.htmlSSO 是一个非常大的主题,我对这个主题有着深深的感受,自从广州 UserGr ...

  6. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

  7. Mysql锁原理浅谈

    锁类型/引擎 行锁 表锁 页锁 MyISAM 有 InnoDB 有 有 BDB(被InnoDB取代) 有 有 锁的分类 表锁:开销小,加锁快,不会死锁,粒度大,冲突率高,并发低. 行锁:开销大,加锁慢 ...

  8. php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  9. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  10. Docker 基础底层架构浅谈

    docker学习过程中,免不了需要学习下docker的底层技术,今天我们来记录下docker的底层架构吧! 从上图我们可以看到,docker依赖于linux内核的三个基本技术:namespaces.C ...

随机推荐

  1. python 快速比较大文件的元素异同之处

    0x00 问题 0x01 解决方法 0x02 list最多可以存放多少条数据呢? 0x03 集合set的操作 0x00 问题 假如,在有两个大文件分别存储了大量的数据,数据其实很简单就是一堆字符串,每 ...

  2. (Python基础教程之四)Python中的变量的使用

    Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...

  3. 如何使用建造者模式(Builder Pattern)创建不可变类

    本文由 ImportNew - 唐小娟 翻译自 Journaldev.如需转载本文,请先参见文章末尾处的转载要求. ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Java ...

  4. vue2-路由Router

    ​ Vue 中的路由用于实现单页应用(SPA)中的页面导航.它允许你在不刷新整个页面的情况下,根据不同的 URL 路径显示不同的组件,提供了类似于多页面应用的用户体验.例如,在一个电商应用中,可以通过 ...

  5. HZNU Winter Trainning 7 补题 - Zeoy

    CodeForces - 1660C 题目传送门:https://vjudge.net/contest/535955#problem/C 题意:询问一个字符串最少删去几个字符,能够把这个字符串变成aa ...

  6. IntelliJ IDEA 中 ctrl + w 一键选中双引号中的字符串内容

    记录下,之前一直知道在 IntelliJ IDEA 中快速选中一个词的快捷键是 ctrl + w,可是有时我们想一键选中双引号中的字符串内容,正好这个字符串中的内容有各种特殊字符,比如",& ...

  7. solon 集成 kafka-clients

    使用 kafka-clients 原本是比较简单的事情.但有些同学习惯了 spring-kafka 后,对原始 java 接口会陌生些.会希望有个集成的示例. <dependency> & ...

  8. Pytorch 手写数字识别 深度学习基础分享

    本篇是一次内部分享,给项目开发的同事分享什么是深度学习.用最简单的手写数字识别做例子,讲解了大概的原理. 手写数字识别 展示首先数字识别项目的使用.项目实现过程: 训练出模型 准备html手写板 fl ...

  9. sqlserver配置分发实现主备

    方案总体说明 本方案采用"发布-订阅模式" 由主服务器进行发布消息,备份服务器进行订阅 当主服务器数据发生变更时,就会发布消息,备份服务器读取消息进行同步更新,中间过程延迟比较短. ...

  10. Navicat连接Oracle数据库报错:oracle library is not loaded解决方法

    连接Oracle时提示"oracle library is not loaded". 去Oracle官网下载Oracle Instant Client Downloads. htt ...