托管代码中是否可能存在内存泄漏? (特别是C#3.0)

时间:2011-06-22 07:51:17

标签: c# .net memory-leaks

例如,如果我有一个分层数据结构:

class Node
{
    public List<Node> children;
}

然后在其中一个父母中填充到多个级别:

myNode.children.Clear();

这将清除所有对直系孩子的提及 - 但那些直系孩子所引用的所有大孩子,大孙子等等呢? C#是否足够聪明,知道它们不再需要它们会被垃圾收集?

我已经阅读过使用WPF数据绑定而没有实现接口INotifyChanged会导致内存泄漏:http://blogs.msdn.com/b/micmcd/archive/2008/03/07/avoiding-a-wpf-memory-leak-with-databinding-black-magic.aspx,在托管环境中如何实现?

11 个答案:

答案 0 :(得分:14)

是的,垃圾收集器会发现孙子等是垃圾。基本上,如果没有办法到达某个对象,它就被认为是垃圾并且有资格收集。

关于托管代码中内存“泄漏”的可能性 - 通常情况下,如果您最终得到一个 可通过对象引用访问的对象,但是在那里您无法最终“清除” “通过API进行的那些引用。

您引用的博文中就是这种情况:

  

WPF检查是否存在查找实现INotifyProperyChanged的内容的问题。如果存在对未实现此接口的数据绑定,则它在全局表中创建记录。该记录未被清除,因为WPF无法检查何时不再需要该DB记录。

因此,这个全局表维护引用,并且您无法指示表中的项可以被清除。

答案 1 :(得分:6)

C#并不关心。这是CLR完成GC的工作。

GC从已知的根对象(静态字段,局部变量,...)开始,并遍历引用,直到找到所有可到达的对象。可以收集所有其他对象(不包括一些与终结器相关的东西)。

因此,如果子引用实际上是对这些对象的唯一引用,那么也将收集大孩子。但是如果一些活着的外部对象仍然引用了你的一个节点,那么这个节点和它引用的所有其他对象都将保持活动状态。


托管内存泄漏是由保持对象存活的引用引起的。

例如,在使用数据库时,GUI会引用对象,使其保持活动状态。

类似地,订阅事件会使与事件处理程序关联的对象保持活动状态。所以有时事件使用弱引用来避免这个问题。

答案 2 :(得分:6)

垃圾收集器只收集不再使用的对象 - 内存泄漏是由仍持有对象引用的对象引起的,即使它们不应该。

在你的情况下,如果另一个对象使用了一个大孩子,那么.Clear会将它从节点列表中删除,但垃圾收集器不会收集它。它会收集所有其他的大孩子。

示例:

class Foo {
 public Node SomeProperty {get; set;}

    public void SomeFunction(){
        var node = new Node { children = new List<Node>() };
        var childNode = new Node();
        var childNode2 = new Node();
        node.children.Add(childNode);
        node.children.Add(childNode2);
        SomeProperty = childNode2;

        node.children.Clear();
        // childNode will be garbage collected
        // childNode2 is still used by SomeProperty,
        // so it won't be garbage collected until SomeProperty or the instance
        // of Foo is no longer used.
    }
}

答案 3 :(得分:2)

另外,如果您使用 unsafe 关键字,也可能会在.net中出现内存泄漏。如果你以与c ++等相同的方式使用指针,并且不小心确保你没有“松散”指针引用,那么GC将无法收集它。

不安全阻止的例子;

unsafe
{
int * ptr1, ptr2;
ptr1 = &var1;
ptr2 = ptr1;
*ptr2 = 20;
}

答案 4 :(得分:2)

当然,C#特别缺少每个人都讨论的其他参考分配内容,如果你有一个包装本地资源的类但它从未被丢弃(或者你丢失了对它的引用),你可以创建一个泄漏。

以下是Image类的一个示例:

public static void MemLeak()
{
    var src = @"C:\users\devshorts\desktop\bigImage.jpg";

    Image image1 = null;

    foreach (var i in Enumerable.Range(0, 10))
    {
        image1 = Image.FromFile(src);
    }

    image1.Dispose();

    Console.ReadLine();
}

Image是一次性的,所以因为我在最后处理图像时不应该有泄漏吗?实际上,每次使用新图像覆盖引用这一事实意味着您无法处理旧图像引用所持有的基础GDI +资源。这将引入内存泄漏。

由于gc doesn't call dispose for youImage类没有覆盖Finalize方法(并在那里调用Dispose),所以你自己就是泄密了。

答案 5 :(得分:0)

是的,您可以拥有一个完整的对象图表(海量数据结构),但如果它们都没有被绑定或引用,它将被垃圾收集。

如果不是,则GC未运行(您可以尝试使用GC.Collect()进行诊断,但不应在生产代码中使用它)或某些内容指的是结构的一部分。例如,UI可能绑定到它。

答案 6 :(得分:0)

循环引用对于.NET中的GC没有问题。它使用一种算法来确定哪些对象实际上可以从某些入口点到达(例如主方法)。

然而,导致物理泄漏的是由静态成员意外引用的对象。

您的示例属于第一类,因此可以安全使用。

答案 7 :(得分:0)

.NET中可能存在一种内存泄漏。

如果您有一个对象“A”注册到另一个对象“B”上的事件,那么“B”将获得对“A”的引用,并且如果您在“A”时未注销该事件将继续这样做“超出了范围。在这种情况下,“A”不能被垃圾收集,因为仍然存在活动引用。它将一直存在,直到“B”被垃圾收集。

如果您遇到“A”对象被创建并且不断超出范围的情况,您将在内存中获得越来越多的“A”。

答案 8 :(得分:0)

我建议阅读.net世界中如何处理垃圾收集 - 实际上,它通过以下引用来查找可以被顶级对象引用的任何内容并释放其他一切;它不适用于像C ++这样的析构函数,因此,如果托管对象的父级和祖父级被释放,那么管理对象将“只是去”这些知识,你会感到高兴。

当然,垃圾收集器只知道托管内存,如果你有任何非托管资源,值得查看IDisposable pattern - 这允许确定性释放非托管对象。

在处理可以引用某个对象的内容时会遇到一个复杂的问题,它确实包含一些不太明显的事情,比如事件处理程序,这就是你提到的WPF / INotifyPropertyChanged问题所在。

答案 9 :(得分:0)

内存泄漏基本上是一段内存,不再需要程序的正常行为,但由于编程错误而无法释放。因此,内存泄漏的概念与垃圾收集,C#或Java无关。

举个例子:

var list = new List<Node>();
Node a1 = new Node();
Node a2 = new Node();
// ...
Node an = new Node();

// Populate list
list.Add(a1);
list.Add(a2);
// ...
list.Add(an);

// use this list
DoStuffTo(list);

// clear list -- release all elements
list.Clear();

// memory leaks from now on

请注意列表中的元素是内存泄漏,因为它们由变量a1 ... an

引用

这只是一个简单的例子,说明为什么它不仅仅取决于C#来处理内存泄漏。开发人员也有责任解决这个问题:

// Clear references
a1 = null;
a2 = null;
// ...
an = null;

这将告诉C#垃圾收集器应该收集所有这些元素。

答案 10 :(得分:0)

是的,一旦不再需要这些对象,就无法正确删除对象的引用,从而导致C#中的泄漏。如果删除了对象的引用,则垃圾收集器在运行时将其除去(它会根据经过仔细调整的算法自动执行此操作,因此最好不要手动使其运行,除非你真的知道你在做什么!)。但是如果没有正确删除对象的引用,垃圾收集器仍然认为应用程序需要它,因此内存泄漏。特别常见的是,事件处理程序发现这种情况并没有得到妥善解决。如果带有子/孙的对象删除了对它的所有引用,那么下次运行垃圾收集器时也会删除该对象以及所有这些子/孙子(除非它们也从其他地方引用)。 / p>

最好的方法是使用内存分析器,它可以让你查看哪些对象在内存中保存其他对象(大多数让你拍摄内存快照,然后查看显示引用的某种图形。如果一个对象仍然存在当它不应该存在时,你可以查看一个图表,显示在内存中保存该对象的引用,并使用它来确定应该清除引用的位置以避免内存泄漏。有一些分析器可用但我可以通过红门找到蚂蚁记忆分析器最容易使用http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/