c# LOH 大对象堆和高并发性能的关系

来源:这里教程网 时间:2026-02-21 17:39:15 作者:

LOH 是什么,为什么高并发下它会成为瓶颈

LOH(Large Object Heap)是 .NET 中专门存放 ≥ 85,000 字节对象的内存区域。它不参与常规 GC 的 compact 阶段,意味着一旦分配,即使后续被回收,留下的碎片也不会被自动整理。在高并发场景下,频繁创建大对象(如

byte[]
string
、大型 DTO 序列化结果)会导致 LOH 快速膨胀、碎片加剧,最终触发 full GC(即 blocking GC),暂停所有线程 —— 这就是吞吐骤降、延迟毛刺的常见根源。

哪些操作会在高并发中意外触发 LOH 分配

很多看似“轻量”的操作,在高并发放大后会悄悄落入 LOH。关键不是“你写了 new byte[100000]”,而是框架/库在背后替你干了这事:

HttpClient.GetStringAsync()
返回的
string
如果响应体超 85KB,字符串本身进 LOH(UTF-16 编码,实际阈值 ≈ 42,500 字符)
JsonSerializer.Serialize(obj)
输出的
string
byte[]
容易越界,尤其嵌套深、字段多的对象
MemoryStream.ToArray()
—— 每次调用都复制整个缓冲区到新
byte[]
,极易命中 LOH
ASP.NET Core 中
ActionResult<t></t>
自动序列化返回值,T 若含大集合或长文本,风险极高

怎么查证你的服务正在被 LOH 拖累

别猜,用工具看真实行为。最直接的方式是启用 .NET 运行时事件追踪:

dotnet-trace collect --process-id <pid> --providers Microsoft-DotNETCore-EventPipe::0x1000000000000000:4

然后用

dotnet-counters monitor -p <pid></pid>
观察关键指标:

LOH Size
:持续增长且不回落 → 内存泄漏或碎片堆积
% Time in GC
> 5% 且伴随
Gen 2 GC Count
频繁上升 → LOH 触发 full GC
Alloc Rate / sec
突增时,若
LOH Alloc Rate / sec
同步飙升 → 确认是大对象主导分配

注意:.NET 6+ 默认启用

GCHeapHardLimit
和 LOH 压缩(需显式开启),但压缩本身有开销,不能无脑打开。

真正有效的缓解策略,不是避免大对象,而是绕过它

高并发服务的关键不是“不分配大对象”,而是“不让大对象生命周期和请求强绑定”。实操上优先考虑流式处理与池化:

Stream
替代
byte[]
:比如
JsonSerializer.SerializeAsync(stream, obj)
直接写入响应流,不缓存完整 payload
复用
ArrayPool<byte>.Shared.Rent()</byte>
分配缓冲区,严格
.Return()
,避免每次 new
对高频大字符串场景,改用
ReadOnlyMemory<char></char>
+
Span<char></char>
解析,跳过 string 实例化
ASP.NET Core 中启用
ResponseCompression
可显著减小传输体积,间接降低序列化后对象大小

LOH 问题从来不是孤立的内存问题,它是并发模型、序列化方式、IO 路径共同作用的结果。最危险的不是某次分配,而是把 LOH 分配藏在中间件、过滤器或通用泛型方法里 —— 看似安全,压测一跑就崩。

相关推荐