在运行时查找对象的引用

23

我有一个对象,它会一直存在。在使用完后,我正在删除所有我能看到的与它相关的引用,但它仍然没有被回收。它的生命周期非常复杂,所以我无法确定是否已清除所有引用。

if ( container.Controls.Count > 0 )
{ 
    var controls = new Control[ container.Controls.Count ];
    container.Controls.CopyTo( controls, 0 );

    foreach ( var control in controls ) 
    { 
         container.Controls.Remove( control );
         control.Dispose();
    }

    controls = null; 
}

GC.Collect();
GC.Collect(1);
GC.Collect(2);
GC.Collect(3);

如何查找一个对象还有哪些引用?为什么它没有被垃圾收集器回收?


展示你的代码,我们可能能够提供帮助。请记住,垃圾回收并不一定会立即发生。 - Lazarus
我想真正的问题是,你为什么要担心这个呢?如果你正在使用一次性资源,请在不再使用它们时将其处理掉,清理非托管系统资源,并小心使用字符串池。 - Yannick Motton
代码如下:if ( container.Controls.Count > 0 ) { var controls = new Control[ container.Controls.Count ]; container.Controls.CopyTo( controls, 0 ); foreach ( var control in controls ) { container.Controls.Remove( control ); control.Dispose();
} controls = null; } GC.Collect(); GC.Collect(1); GC.Collect(2); GC.Collect(3); 但它仍然在内存中。这意味着它仍然有根源。我该如何找到这些根源?
- er-v
你确定它在内存中吗?虚拟机可能不会释放它所使用的内存。 - user7116
代码如下:` if ( container.Controls.Count > 0 ) { var controls = new Control[ container.Controls.Count ]; container.Controls.CopyTo( controls, 0 ); foreach ( var control in controls ) { container.Controls.Remove( control ); control.Dispose();
} controls = null; }GC.Collect(); GC.Collect(1); GC.Collect(2); GC.Collect(3); `但它仍然在内存中。这意味着它仍然有根源。我该如何找到这些根源?
- er-v
显示剩余3条评论
6个回答

15

建议使用内存分析工具(例如ants),它可以告诉您哪些对象在占用内存。试图猜测和解决这类问题是非常困难的。

Red-gate 提供 14 天免费试用,这应该足够时间来解决此问题并决定内存分析工具是否为您提供长期价值。

市场上有很多其他内存分析工具(例如.NET Memory Profiler)大部分都有免费试用版,但我发现Red-Gate 的工具易于使用,因此首选尝试使用它们。


他们还有一些免费的培训视频(和文档)等,可以解释 .net 垃圾回收器的工作原理,这可能对你很有用。 - Ian Ringrose
@er-v:你的对象可能已经被回收了,但是内存可能还没有被 Windows 回收。框架不必将内存返回给操作系统。 - user7116

5

我曾经用SOS扩展解决过类似的问题(但显然该扩展已不再支持Visual Studio 2013,但在旧版Visual Studio中可正常使用)。

我使用了以下代码来获取我想要跟踪引用的对象地址:

public static string GetAddress(object o)
{
    if (o == null)
    {
        return "00000000";
    }
    else
    {
        unsafe
        {
            System.TypedReference tr = __makeref(o);
            System.IntPtr ptr = **(System.IntPtr**) (&tr);
            return ptr.ToString ("X");
        }
    }
}

然后,在使用调试器运行时,打开 Visual Studio 2012 的立即窗口,并输入以下内容:

.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll

该操作将加载SOS.dll扩展库。

接下来,您可以使用GetAddress(x)获取对象的十六进制地址(例如8AB0CD40),然后使用:

!do 8AB0CD40
!GCRoot -all 8AB0CD40

将对象转储并查找对该对象的所有引用。

请记住,如果GC运行,则可能会更改对象的地址。


4
你需要使用 WindbgSosex 扩展程序。 !DumpHeap!GCRoot 命令可以帮助你识别实例以及所有保持其存活的剩余引用。

3

我一直在使用.NET Memory Profiler对我们的一个项目进行严格的内存分析。它是一个很好的工具,可以深入了解应用程序的内存管理情况。我并没有因此获得报酬 :) 但它确实帮助了我很多。


同意,这是一个非常有用的工具,特别是视觉方面的东西。 - Stephen Drew

2
在.NET中,垃圾回收不是像COM那样的计数方案,而是采用标记-清除实现。基本上,GC会在它感觉需要进行垃圾回收时“随机”运行,因此对象的收集并不是确定性的。
然而,您可以手动触发垃圾回收(GC.Collect()),但是您可能需要等待finalizer运行(GC.WaitForPendingFinalizers())。然而,在生产应用程序中这样做是不鼓励的,因为它可能会影响内存管理的效率(GC运行太频繁或等待finalizer运行)。如果对象仍然存在,则它实际上仍然具有某个活动引用。

0

它未被回收是因为你没有删除所有对它的引用。只有在应用程序中没有根引用的对象才会被GC标记为可回收。

你正在使用哪些方法来检查GC是否已经收集了你的对象?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接