C#中的内存泄漏

时间:2009-03-06 22:38:58

标签: c# .net memory-leaks garbage-collection managed

当您确保所有句柄,实现IDispose的内容都被处理掉时,管理系统中是否有可能泄漏内存?

是否会出现遗漏某些变量的情况?

21 个答案:

答案 0 :(得分:63)

事件处理程序是非显而易见的内存泄漏的常见来源。如果你从object2订阅object1上的事件,那么执行object2.Dispose()并假装它不存在(并从你的代码中删除所有引用),在object1的事件中有一个隐式引用会阻止object2被垃圾收集。

MyType object2 = new MyType();

// ...
object1.SomeEvent += object2.myEventHandler;
// ...

// Should call this
// object1.SomeEvent -= object2.myEventHandler;

object2.Dispose();

这是泄漏的常见情况 - 忘记轻松取消订阅事件。当然,如果收集了object1,也会收集object2,但直到那时才收集。

答案 1 :(得分:40)

我不认为C ++风格的内存泄漏是可能的。垃圾收集器应该考虑到这些。即使永远不再使用对象,也可以创建聚合对象引用的静态对象。像这样:

public static class SomethingFactory
{
    private static List<Something> listOfSomethings = new List<Something>();

    public static Something CreateSomething()
    {
        var something = new Something();
        listOfSomethings.Add(something);
        return something;
    }
}

这是一个明显愚蠢的例子,但它相当于托管运行时内存泄漏。

答案 2 :(得分:27)

正如其他人所指出的那样,只要内存管理器中没有实际的错误,不使用非托管资源的类就不会泄漏内存。

您在.NET中看到的不是内存泄漏,而是永远不会被丢弃的对象。只要垃圾收集器可以在对象图上找到它,就不会丢弃对象。因此,如果任何生物对象都有对象的引用,它就不会被处理掉。

事件注册是实现这一目标的好方法。如果一个对象注册了一个事件,那么它注册的任何事件都会引用它,即使你删除了对该对象的所有其他引用,直到它取消注册(或者它注册的对象变得无法访问)它将保持活跃状态​​。

因此,您必须在不知情的情况下注意注册静态事件的对象。例如,ToolStrip的一个漂亮功能是,如果您更改显示主题,它将自动在新主题中重新显示。它通过注册静态SystemEvents.UserPreferenceChanged事件来完成这个漂亮的功能。当您更改Windows主题时,会引发该事件,并且所有正在侦听该事件的ToolStrip个对象都会收到有关新主题的通知。

好的,假设您决定在表单上丢弃ToolStrip

private void DiscardMyToolstrip()
{
    Controls.Remove("MyToolStrip");
}

你现在有一个永远不会死的ToolStrip。即使它不再出现在您的表单上,每次用户更改主题时,Windows都会尽职尽责地告诉其他不存在的ToolStrip。每次垃圾收集器运行时,它都会认为“我无法抛弃该对象,UserPreferenceChanged事件正在使用它。”

这不是内存泄漏。但它可能也是。

这样的事情使记忆探测器变得无价。运行一个内存分析器,你会说“这很奇怪,堆上似乎有一万个ToolStrip对象,即使我的表单上只有一个。这是怎么发生的?”

哦,如果你想知道为什么有些人认为属性设置者是邪恶的:要从ToolStrip事件中取出UserPreferenceChanged取消注册,请将其Visible属性设置为{ {1}}。

答案 3 :(得分:20)

代表可能会导致内核泄漏不直观。

每当您从实例方法创建委托时,对该实例的引用都存储在该委托的“in”中。

此外,如果将多个委托组合到一个多播委托中,只要在某个地方使用多播委托,就会有大量对大量对象的引用,这些对象不会被垃圾回收。

答案 4 :(得分:17)

如果您正在开发WinForms应用程序,则微妙的“泄漏”是Control.AllowDrop属性(用于启用拖放)。如果AllowDrop设置为“true”,则CLR仍会通过System.Windows.Forms.DropTarget保留在您的控件上。要解决此问题,请确保Control的{​​{1}}属性设置为AllowDrop,当您不再需要它时,CLR将负责其余的工作。

答案 5 :(得分:7)

如前所述,保持引用会导致内存使用量增加。进入这种情况的一个简单方法是使用事件。如果你有一个长生命对象,其他对象听到的事件,如果从不删除监听器,那么长寿命对象上的事件将使这些其他实例在不再需要它们之后保持很长时间。

答案 6 :(得分:7)

.NET应用程序中内存泄漏的唯一原因是,尽管它们的生命周期已经结束,但仍然会引用对象。因此,垃圾收集器无法收集它们。它们成为长寿的物体。

我发现在对象的生命结束时订阅事件而不取消订阅会导致泄漏很容易。

答案 7 :(得分:6)

答案 8 :(得分:5)

Reflection emit是泄漏的另一个潜在来源,例如内置的对象反序列化器和花哨的SOAP / XML客户端。至少在早期版本的框架中,从属AppDomains中生成的代码从未被卸载......

答案 9 :(得分:4)

这是一个神话,你不能泄漏托管代码中的内存。当然,它比非托管C ++更难,但有一百万种方法可以做到这一点。静态对象持有引用,不必要的引用,缓存等。如果你正在以“正确”的方式做事,你的许多对象将不会得到垃圾收集,直到必要的时间晚,这在我看来也是一种内存泄漏,以实际而非理论的方式。

幸运的是,有些工具可以帮助您。我经常使用微软的CLR Profiler - 它不是用户编写的最友好的工具,但它绝对非常实用且免费。

答案 10 :(得分:2)

一旦对象的所有引用都消失了,垃圾收集器就会在下一次传递时释放该对象。我不会说泄漏记忆是不可能的,但是这很难,为了泄漏你必须在没有意识到的情况下引用一个物体。

例如,如果您将对象实例化为列表,然后在完成后忘记将它们从列表中删除并忘记丢弃它们。

答案 11 :(得分:1)

如果未正确管理非托管资源,则可能会发生泄漏。实现IDisposable的类可能会泄漏。

但是,常规对象引用不像低级语言那样需要显式内存管理。

答案 12 :(得分:1)

实际上并不是内存泄漏,但是在使用大型对象时会很容易耗尽内存(如果我没记错的话,大于64K)。它们存储在LOH上,不进行碎片整理。因此,使用这些大对象并释放它们可以释放LOH上的内存,但.NET运行时不再使用该可用内存用于此过程。因此,只需使用LOH上的几个大物体,就可以轻松地耗尽LOH上的空间。这个问题为微软所知,但我记得现在正在计划解决方案。

答案 13 :(得分:0)

唯一的泄漏(除了可能存在的运行时中的错误,尽管不太可能是由于垃圾收集)将是本机资源。如果你P / Invoke到一个本机库,它代表托管应用程序打开文件句柄或套接字连接,或者你从未明确地关闭它们(并且不在处理器或析构函数/终结器中处理它们),你可以有内存或资源泄漏,因为运行时无法自动管理所有这些内容。

如果您坚持使用纯粹的托管资源,那么您应该没问题。如果你遇到任何形式的内存泄漏而没有调用本机代码,那就是一个bug。

答案 14 :(得分:0)

虽然框架中的某些东西可能存在泄漏,但更多的可能是你没有正确处理某些东西或阻止GC处理它的东西,IIS将成为这个的主要候选者。

请记住,并非.NET中的所有内容都是完全托管代码,COM互操作,文件流,文件流,数据库请求,图像等等。

我们之前遇到的一个问题(IIS 6上的.net 2.0)是我们会创建一个映像然后处理它但IIS不会释放内存一段时间。

答案 15 :(得分:0)

在我上一份工作中,我们使用的是第三方.NET SQLite库,它像筛子一样泄露。

我们在一个奇怪的情况下进行了大量的快速数据插入,每次都必须打开和关闭数据库连接。第三方lib做了一些相同的连接打开,我们应该手动完成并且没有记录它。它还在我们从未找到的地方举行了参考。结果是打开的连接数量是原来的2倍,只有1/2被关闭。自从引用被引用后,我们就发生了内存泄漏。

这显然与传统的C / C ++内存泄漏不同,但对于所有意图和目的来说,它对我们来说都是一个。

答案 16 :(得分:0)

如果它被认为是内存泄漏,也可以用这种代码来实现:

public class A
{
    B b;
    public A(B b) { this.b = b; }
    ~A()
    {
        b = new B();
    }
}

public class B
{
    A a;
    public B() { this.a = new A(this); }
    ~B()
    {
        a = new A(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        {
            B[] toBeLost = new B[100000000];
            foreach (var c in toBeLost)
            {
                toBeLost.ToString(); //to make JIT compiler run the instantiation above
            }
        }
        Console.ReadLine();
    }
}

答案 17 :(得分:0)

小功能有助于避免“内存泄漏”。因为垃圾收集器在函数末尾释放局部变量。如果函数很大并占用大量内存,你自己必须释放占用大量内存且不再需要的局部变量。类似的全局变量(数组,列表)也很糟糕。

在创建图像而不处理图像时,我在C#中遇到了内存泄漏。哪个有点奇怪。人们说你必须在拥有它的每个对象上调用.Dispose()。但是图形C#函数的文档并不总是提到这一点,例如函数GetThumbnailImage()。我认为C#编译器应该警告你。

答案 18 :(得分:0)

自我提醒 如何找到内存泄漏:

  • 删除和gc.collect调用。
  • 等到我们确定内存泄漏。
  • 从任务管理器创建转储文件。
  • 使用DebugDiag打开转储文件。
  • 首先分析结果。那里的结果应该有助于我们,这通常需要大部分内存。
  • 修复代码,直到找不到内存泄漏。
  • 使用第三方应用程序,例如.net profiler。 (我们可以使用试用版,但需要尽快解决问题。第一个转储应该主要帮助我们如何泄漏)
  • 如果问题出在虚拟内存中,需要观看非托管内存。 (通常需要启用其他配置)
  • 根据使用方式运行第三方应用程序。

常见的内存泄漏问题:

  • 永远不会删除事件/代理。 (处置时,请确保该事件未注册) - 请参阅ReepChopsey answer
  • 列表/词典从未被清除。
  • 引用保存在内存中的另一个对象的对象永远不会被释放。 (克隆它以便于管理)

答案 19 :(得分:0)

在控制台或Win应用程序中,创建一个Panel对象(panel1),然后添加设置了PictureBox属性的1000 Image,然后调用panel1.Controls.Clear。所有PictureBox控件仍在内存中,GC无法收集它们:

var panel1 = new Panel();
var image = Image.FromFile("image/heavy.png");
for(var i = 0; i < 1000;++i){
  panel1.Controls.Add(new PictureBox(){Image = image});
}
panel1.Controls.Clear(); // => Memory Leak!

正确的方法是

for (int i = panel1.Controls.Count-1; i >= 0; --i)
   panel1.Controls[i].Dispose();

Memory leaks in calling Controls.Clear()

  

调用Clear方法不会从内存中删除控件句柄。   您必须显式调用Dispose方法,以避免内存泄漏

答案 20 :(得分:0)

使用.NET XmlSerializer时可能会发生内存泄漏,因为它在下面使用了非托管代码,这些代码将不会被处理。

在此页面上查看文档并搜索“内存泄漏”:

RDOReportItem