实用技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > ASP.NET > 实用技巧 > .net垃圾回收

你真的理解 .NET 的垃圾回收吗 .NET垃圾回收机制详解

作者:波波007

.NET垃圾回收(GC)是自动内存管理机制,通过分代式标记-压缩算法管理托管内存,了解GC的工作机制,可以设计更高效的对象生命周期,避免内存碎片和长生命周期陷阱,并快速定位内存问题,本文介绍.NET垃圾回收机制,感兴趣的朋友跟随小编一起看看吧

在 .NET 开发中,我们经常听到一句话:“内存不用管,GC 会帮你处理。”这句话对,也不完全对。这也是面试常考的问题。

确实,.NET 通过垃圾回收器(Garbage Collector,简称 GC)实现了自动内存管理,开发者不需要像 C/C++ 那样手动 free 或 delete。但如果不了解 GC 的工作机制,就很容易在高并发、低延迟或长时间运行的系统中踩坑。

真正优秀的 .NET 开发者,不是“依赖 GC”,而是“理解 GC、配合 GC”。

什么是垃圾回收(GC)?

垃圾回收是 .NET 运行时提供的一套自动内存管理机制。它主要负责:

简单来说,GC 会帮你回收“已经没人用”的对象,保证程序长期运行不会因为内存问题崩溃。

需要强调的是:GC 管理的是托管内存(Managed Memory),也就是通过 new 创建的对象。

.NET GC 的工作机制

.NET 使用的是分代式标记-压缩算法(Generational Mark-and-Compact)。它的设计基于一个非常重要的事实:

绝大多数对象的生命周期都很短。

比如 Web 请求中的 DTO、局部变量、临时字符串,往往在一次请求结束后就可以回收。

一、分代机制(Gen 0 / Gen 1 / Gen 2)

.NET 将对象按“存活时间”划分为三代:

当 Gen 0 空间不足时,会触发一次 Gen 0 回收。如果对象在回收后仍然存活,就会被“晋升”到 Gen 1。再存活则进入 Gen 2。

这种设计的好处是: 优先回收“短命对象”,减少扫描范围,大幅提升效率。

二、标记阶段(Mark Phase)

GC 会短暂停止应用程序(Stop-The-World),从一组“根对象”(Root)开始遍历,比如:

所有能从根对象访问到的对象都会被标记为“存活”。没有被标记的对象就是垃圾。

三、压缩阶段(Compact Phase)

标记完成后,GC 会把存活对象移动到内存的一端,让内存保持连续。

这样做有两个好处:

这也是 .NET 内存分配速度非常快的重要原因。

四、大对象堆(LOH)

当对象大小超过 85KB 时,会被分配到大对象堆(LOH,Large Object Heap)。

LOH 有两个特点:

例如:

如果频繁创建 100KB 以上的对象,就可能造成内存碎片问题。

建议做法是:使用对象池复用大对象,比如 ArrayPool<T>

GC 何时触发?

GC 由运行时自动决定,常见触发场景包括:

一般情况下,GC 的调度策略已经非常智能,开发者不需要也不应该干预

为什么不推荐手动调用 GC.Collect()?

很多人觉得:“我手动 GC 一下,内存马上就干净了。”

实际上,这通常是反优化行为。

调用 GC.Collect() 会:

除非在极特殊场景(例如游戏关卡切换、大量对象刚刚释放)并经过充分测试,否则不建议使用。

与 GC 协同的最佳实践

理解 GC 的目的,是为了“配合它”,而不是“对抗它”。

一、确定性释放非托管资源

文件句柄、数据库连接、Socket 等属于非托管资源,必须及时释放。

应使用 using 或 IDisposable

using (var stream = new FileStream("test.txt", FileMode.Open))
{
    // 使用文件
}

using 会在作用域结束时自动调用 Dispose(),而不是等待 GC 回收。

二、避免频繁分配大对象

例如:

var buffer = new byte[100_000]; // 进入 LOH

高频分配大数组会增加 LOH 压力。

推荐使用对象池:

var pool = ArrayPool<byte>.Shared;
var buffer = pool.Rent(100_000);
try
{
    // 使用 buffer
}
finally
{
    pool.Return(buffer);
}

三、警惕长生命周期引用

以下对象会长期存活:

如果它们持有大对象引用,就会阻止 GC 回收,造成“逻辑内存泄漏”。

四、合理使用依赖注入生命周期

在 ASP.NET Core 中:

不要在 Singleton 服务中持有大量可变数据,否则容易进入 Gen 2 并长期占用内存。

典型场景分析

Web API 高并发请求

请求上下文、DTO 等对象集中在 Gen 0。 GC 回收非常高效,通常无需干预。

图像 / 视频处理

大量字节数组进入 LOH。 应使用 MemoryPool<byte> 或 ArrayPool<byte> 进行缓冲区复用。

长期运行的后台服务

Gen 2 对象不断累积。 应重点检查:

IDisposable 与 GC 的关系澄清

很多开发者会混淆 IDisposable 和 GC。

其实它们解决的是两个不同的问题:

GC 何时运行不可预测,而 Dispose() 是确定性的。

终结器(Finalizer)只是最后的“保险机制”,不应依赖。

一句话总结:

GC 负责“内存”,Dispose 负责“资源”。

结语

真正理解 GC,你会获得三项能力:

在高并发、云原生、微服务时代, 对 GC 的理解已经不只是“基础知识”,而是工程能力的一部分。

写代码不仅是“功能实现”,更是对资源的掌控。

参考资料

① Microsoft. Fundamentals of Garbage Collection.https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

② Maoni Stephens. CLR Inside Out: Large Object Heap Uncovered. MSDN Magazine, 2008.

③ Microsoft. Large Object Heap.https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap

④ Ben Watson. Writing High-Performance .NET Code. 2nd ed., 2018.

⑤ Microsoft. Implementing a Dispose method.https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose

到此这篇关于你真的理解 .NET 的垃圾回收吗?的文章就介绍到这了,更多相关.net垃圾回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文