.NET Profiler未标识控制器操作循环引用的问题咨询
关于.NET循环引用对象被GC回收的疑惑解答
这问题我之前也碰到过,一开始也误以为循环引用会导致内存泄漏,理清楚逻辑后就明白为啥你看到存活实例数为0了~
首先明确:.NET GC完全能处理循环引用
.NET的垃圾回收器是追踪式回收,它会从「根对象」(比如静态变量、栈上的局部变量、CPU寄存器中的引用等)出发,遍历所有可达的对象。如果一组对象互相形成循环引用,但没有任何根对象指向这个循环链里的任何一个对象,那整个循环链都会被标记为可回收资源,GC会正常清理它们。
你的代码场景分析
看你贴的控制器代码:
[AllowAnonymous] public ActionResult Index() { var b = new B(); var c = new C(); b.x = c; c.x = b; return PartialView(); }
这里的b和c都是方法内的局部变量,它们的作用域仅限于Index方法执行期间。当方法执行完毕,对应的栈帧会被弹出,这两个变量的引用就从根集合中消失了——也就是说,整个b-c循环链没有任何根引用指向它们,GC完全可以把它们回收掉。
为啥Profiler和DotMem显示存活数为0?
这是正常现象:
- 当方法执行结束后,.NET的GC可能已经在后台完成了回收(尤其是对于短期存活的对象,会被放在第0代,回收频率很高);
- 如果你是在方法执行完毕后才拍的Profiler快照,那这些对象已经被清理,自然看不到存活实例。
如何验证对象确实被回收了?
可以做两个小测试:
添加析构函数:给B和C类加上析构函数,运行程序看控制台输出:
public class B { public object x; ~B() { Console.WriteLine("B对象被GC回收了"); } } public class C { public object x; ~C() { Console.WriteLine("C对象被GC回收了"); } }调用Index方法后,过一会儿(或者手动触发GC)就能看到控制台输出回收日志。
手动触发GC:在方法末尾手动调用GC(仅调试用,生产环境别这么干):
[AllowAnonymous] public ActionResult Index() { var b = new B(); var c = new C(); b.x = c; c.x = b; // 手动触发GC GC.Collect(); GC.WaitForPendingFinalizers(); return PartialView(); }再拍Profiler快照,依然会看到存活实例数为0,进一步证明对象被回收了。
关于Profiler查看区域的小提示
如果之后遇到真的内存泄漏情况,你可以重点查看这些区域:
- 静态变量是否意外持有对象引用;
- 事件订阅是否没有取消,导致对象被订阅者持有;
- 非托管资源是否没有正确释放(但你的场景里没有非托管资源)。
但在当前这个场景下,你完全没找错区域,就是对象被正常回收了~
内容的提问来源于stack exchange,提问作者Omtechguy
相关产品推荐
相关产品推荐

