c# 高并发应用中的 GC 停顿问题分析和解决
技术百科
星降
发布时间:2026-01-18
浏览: 次 高并发下GC停顿变长主因是Gen 0晋升速率过高引发频繁Gen 1/2回收,叠加LOH碎片化;需用dotnet-trace抓GCSuspendEE与GCStart时间差、观察Gen2PromotedBytesPerSec是否超10MB/s,并组合启用服务器GC、LOH压缩、对象池及避免async隐式堆分配来缓解。
为什么高并发下 GC 停顿会突然变长?
不是因为对象总量大,而是因为 Gen 0 晋升速率过高,导致频繁触发 Gen 1 和 Gen 2 回收。在高吞吐场景(如 Web API、实时消息处理)中,短生命周期对象暴增(比如 JSON 反序列化产生的临时字符串、Dictionary 的内部数组扩容),但若线程局部分配缓冲区(TLAB)耗尽或存在大对象(≥ 85 KB),就会绕过 TLAB 直接进入 LOH(Large Object Heap),而 LOH 只在 Gen 2 GC 时才回收——且不压缩,容易碎片化,进一步诱发更重的 GC。
如何确认是 GC 导致的停顿?
别只看 GC.Collect() 调用日志,要抓真实指标:
- 用
dotnet-trace录制运行时事件:dotnet-trace collect --process-id
,重点关注--providers "Microsoft-Windows-DotNETRuntime:4:4" GCSuspendEEStart/Stop和GCStart/End时间戳差值 - 检查
System.RuntimeEventSource 中的GCHeapStats,观察Gen0Size、Gen2PromotedBytesPerSec是否持续高于 10 MB/s - 用
PerfView打开 trace,筛选GC/AllocTick+GC/Start,看是否出现“小对象分配密集 → 短时间后 Gen0 GC → 紧接着 Ge
n2 GC”的链式反应
关键缓解手段:从代码和配置双路压制
GC 停顿不是调优出来的,是靠约束出来的。以下操作需组合使用:
- 启用服务器 GC(
):为每个逻辑 CPU 分配独立 GC 线程和堆,避免多线程争抢同一 GC 队列;注意它默认开启true Concurrent GC,但在 .NET 6+ 中已被Background GC取代,无需额外关断 - 禁用 LOH 自动压缩(
不推荐)→ 改用true GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce在低峰期手动触发(仅限 .NET Core 3.0+) - 避免隐式大对象:把
new byte[100_000]拆成池化ArrayPool;JSON 反序列化优先用.Shared.Rent(100_000) JsonSerializer.Deserialize而非(Utf8JsonReader) Deserialize,跳过 string → UTF8 字节数组转换(string) - 对高频创建的小对象(如 DTO、上下文容器),用
ObjectPool或MemoryPool,但注意池大小不能无限制增长,需配合.Shared MaxSize限流
最容易被忽略的陷阱:异步 + GC 的隐蔽耦合
看似无 GC 的 async 方法,可能因 async/await 状态机生成闭包捕获局部变量(尤其是 Span、ReadOnlyMemory),导致本该栈分配的对象被迫堆分配。典型案例如:
public async TaskProcessAsync(string input) { var buffer = new byte[4096]; // → LOH! await stream.ReadAsync(buffer, ...); // buffer 被状态机捕获 return Encoding.UTF8.GetString(buffer); // 再次触发 string 分配 }
正确做法是:
- 用
stackalloc byte[4096](仅限unsafe上下文且 size 固定) - 或改用
MemoryPool并确保.Shared.Rent(4096) Return()调用(建议用using) - 避免在
async方法内直接 new 大数组、大字典;把数据准备逻辑提前到同步阶段,或用ValueTask减少状态机开销
GC 停顿从来不是孤立问题——它暴露的是对象生命周期与并发模型的错配。真正有效的优化,往往发生在把一个 new List 替换成 ArrayPool 的那一行,而不是 GC 参数调优的第十个配置项。
# ai
# 的是
# 就会
# 尤其是
# windows
# 已被
# 过高
# 仅限
# win
# microsoft
# js
# json
# 并发
# 对象
# 堆
# String
# int
# 字节
# stream
# c#
# 字符串
# 序列化
# .net
# 为什么
# 线程
# 栈
# 异步
# 事件
# red
# 多线程
# 隐式
# 闭包
# Object
# background
# 局部变量
# using
# 变长
# 链式反应
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- 如何在Golang中使用container/hea
- Win11怎么设置开机密码_Windows11账户
- LINUX如何删除用户和用户组_Linux use
- 如何使用Golang benchmark测量函数延
- php怎么操作Redis_Redis扩展连接与基本
- Win11如何设置文件关联 Win11修改特定文件
- 小程序里php怎么变mp4_小程序调用php生成m
- php下载安装包怎么选_threadsafe与nt
- MySQL 中使用 IF 和 CASE 实现查询字
- php删除数据怎么加限制_带where条件删除避免
- 如何使用Golang读取日志文件_Golang b
- Python文本编码与解码_跨平台解析说明【指导】
- Laravel 查询 JSON 列:高效筛选包含数
- Win11开机自检怎么关闭_跳过Win11开机磁盘
- Windows10如何更改系统字体大小_Win10
- PHP主流架构怎么监控运行状态_工具推荐【操作】
- Windows蓝屏BAD_POOL_HEADER故
- Win11怎么开启剪贴板历史记录_Windows1
- 如何在Golang中捕获HTTP服务器错误_Gol
- 如何在 ACF 中正确更新嵌套多层 Group 字
- C++如何使用Qt创建第一个GUI窗口?(入门教程
- PHP的FastAdmin架构适合二次开发吗_特点
- c++如何判断文件是否存在_c++ filesys
- Win11如何暂停系统更新 Win11暂停更新最长
- C++ STL算法库怎么用?C++常用算法函数(s
- Windows 11怎么更改锁屏超时时间_Wind
- 如何在同包不同文件中正确引用 Go 结构体
- Windows10如何彻底关闭自动更新_Win10
- 如何在Golang中使用闭包_封装变量与函数作用域
- XAMPP 启动失败(Apache 突然停止)的终
- Win11局域网共享怎么设置 Win11文件夹网络
- 如何在 Go 开发中正确处理本地包导入与远程模块路
- 如何使用Golang匿名函数_快速定义临时函数逻辑
- 如何高效删除 NumPy 二维数组中所有元素相同的
- 如何用::实现单例模式_php静态方法与作用域操作
- 如何在Golang中使用内置函数_Golangle
- 如何在Golang中处理数据库事务错误_回滚和日志
- Win11怎么关闭自动维护 Win11禁用系统自动
- Win10如何更改任务栏高度_Windows10解
- Windows10电脑怎么设置电源按钮_Win10
- Python字符串处理进阶_切片方法解析【指导】
- Win11开机Logo怎么换_Win11自定义启动
- Windows10怎么用“讲述人”读屏辅助 Win
- 如何使用Golang理解结构体指针方法接收者_Go
- c++ reinterpret_cast怎么用 c
- php修改数据怎么批量改状态_批量更新status
- Win10怎样清理C盘爱奇艺缓存_Win10清理爱
- Python并发安全问题_资源竞争说明【指导】
- Mac系统更新下载慢或失败怎么办_解决macOS升
- Win11怎么设置单手模式_Win11触控键盘布局


QQ客服