为什么.NET没有内存泄漏?

时间:2010-03-26 19:17:29

标签: .net memory-leaks

忽略不安全的代码,.NET不会有内存泄漏。我从许多专家那里无休止地读到了这一点,我相信它。但是,我不明白为什么会这样。

据我了解,框架本身是用C ++编写的,而C ++容易受到内存泄漏的影响。

  • 底层框架是如此精心编写的,它绝对没有任何内部内存泄漏的可能性?
  • 框架代码中是否存在自我管理甚至可以治愈其内存泄漏的内容?
  • 我还没有考虑过其他的答案吗?

16 个答案:

答案 0 :(得分:94)

.NET 可以发生内存泄漏。

大多数情况下,人们会引用垃圾收集器,它决定何时可以摆脱对象(或整个对象循环)。这避免了经典 c和c ++样式的内存泄漏,我的意思是分配内存而不是以后释放它。

然而,很多时候程序员都没有意识到对象仍然有悬空引用,并且没有收集垃圾,导致......内存泄漏。

通常情况下,事件已注册(使用+=)但稍后未注册,但访问非托管代码时(使用pInvokes或使用底层系统资源的对象,例如文件系统或数据库连接)并且没有妥善处理资源。

答案 1 :(得分:43)

这里已有一些好的答案,但我想再提一点。让我们再仔细看看你的具体问题:


据我了解,框架本身是用C ++编写的,而C ++容易受到内存泄漏的影响。

  • 底层框架是如此精心编写的,它绝对没有任何内部内存泄漏的可能性?
  • 框架代码中是否存在自我管理甚至可以治愈其内存泄漏的内容?
  • 我还没有考虑过其他的答案吗?

这里的关键是区分您的代码和他们的代码。 .Net框架(以及Java,Go,python和其他垃圾收集语言)承诺,如果您依赖他们的代码,您的代码将不会泄漏内存。至少在传统意义上。您可能会发现自己处于某些对象未按预期释放的情况,但这些情况与传统的内存泄漏略有不同,因为程序中的对象仍然可以访问。

你很困惑,因为你正确地理解这与说你创建的任何程序根本不可能有传统的内存泄漏是不一样的。 他们的代码中仍然存在泄漏内存的错误。

所以现在你必须问自己:你宁愿相信你的代码还是他们的代码?请记住,他们的代码不仅仅是由原始开发人员进行测试(就像你的一样,对吗?),它也是由数千(可能是数百万)其他程序员(如自己)日常使用而进行的战斗。任何重大的内存泄漏问题都将是首先确定和纠正的问题之一。再说一遍,我不是说这是不可能的。信任他们的代码通常比自己的代码更好...至少在这方面。

因此,这里的正确答案是它是您第一个建议的变体:

  

底层框架是否编写良好,它绝对没有内部内存泄漏的可能性?

这不是没有可能,但它比自己管理它更安全。我当然不知道框架中有任何已知的泄漏。

答案 2 :(得分:15)

在查看Microsoft文档(特别是“Identifying Memory Leaks in the CLR”)之后,Microsoft确实发表声明,只要您没有在应用程序中实现不安全的代码,就不会发生内存泄漏

现在,他们还指出了感知内存泄漏的概念,或者在评论中指出“资源泄漏”,即使用具有延迟引用且未正确处理的对象。 IO对象,数据集,GUI元素等可能会发生这种情况。在使用.NET时,它通常等同于“内存泄漏”,但它们不是传统意义上的泄漏。

答案 3 :(得分:12)

由于垃圾回收,您不能有常规的内存泄漏(除了特殊情况,如不安全的代码和P / Invoke)。但是,您当然可以无意中保持引用永远存在,有效地泄漏内存。

修改

到目前为止我见过的最好的例子是事件处理程序+ =错误。

修改

请参阅下文,了解错误的解释,以及错误解释为真正泄漏的条件,而不是几乎真正的泄漏。

答案 4 :(得分:4)

以下是.NET中内存泄漏的示例,它不涉及不安全/ pinvoke,甚至不涉及事件处理程序。

假设您正在编写后台服务,该服务通过网络接收一系列消息并对其进行处理。所以你创建了一个类来保存它们。

class Message 
{
  public Message(int id, string text) { MessageId = id; Text = text; }
  public int MessageId { get; private set; }
  public string Text { get; private set; }
}
好的,到目前为止一切顺利。稍后您会发现,如果您在执行处理时引用了之前可用的消息,那么系统中的某些要求可以确保更容易。想要这个可能有很多原因。

所以你添加一个新属性......

class Message
{
  ...
  public Message PreviousMessage { get; private set; }
  ...
}

你编写代码来设置它。当然,在主循环的某个地方,你必须有一个变量来跟上最后一条消息:

  Message lastMessageReceived;

然后你发现有些日子比你的服务遭到轰炸,因为它已经用一连串过时的消息填满了所有可用的内存。

答案 5 :(得分:4)

以下是此人使用ANTS .NET Profiler发现的其他内存泄漏:http://www.simple-talk.com/dotnet/.net-tools/tracing-memory-leaks-in-.net-applications-with-ants-profiler/

答案 6 :(得分:3)

我想可以编写软件,例如.NET运行时环境(CLR),如果足够小心,它不会泄漏内存。但是,由于微软确实不时通过Windows Update向.NET框架发布更新,我很确定即使在CLR中 偶尔会出现错误。

所有软件都可能泄漏内存。

但正如其他人已经指出的那样,还有其他类型的内存泄漏。虽然垃圾收集器负责“经典”内存泄漏,但仍然存在释放所谓的非托管资源(例如数据库连接,打开文件,GUI元素等)的问题。 。这就是IDisposable接口的用武之地。

另外,我最近遇到了可能在 .NET-COM interop 设置中泄漏内存的问题。 COM组件使用引用计数来决定何时可以释放它们。 .NET为此添加了另一个引用计数机制,可以通过静态System.Runtime.InteropServices.Marshal类来影响。

毕竟,即使在.NET程序中,您仍然需要注意资源管理。

答案 7 :(得分:2)

.NET代码中绝对可能存在内存泄漏。在某些情况下,某些对象会自行生根(尽管这些对象通常是IDisposable)。在这种情况下,无法在对象上调用Dispose()将绝对导致实际的C / C ++样式内存泄漏,并且您无法引用已分配的对象。

在某些情况下,某些计时器类可以具有此行为,作为一个示例。

如果您有可能重新安排自己的异步操作,则可能存在泄漏。异步操作通常会根据回调对象进行操作,从而阻止收集。在执行期间,对象由执行线程生根,然后新调度的操作重新生成对象。

以下是使用System.Threading.Timer的一些示例代码。

public class Test
{
    static public int Main(string[] args)
    {
        MakeFoo();
        GC.Collect();
        GC.Collect();
        GC.Collect();
        System.Console.ReadKey();
        return 0;
    }

    private static void MakeFoo()
    {
        Leaker l = new Leaker();
    }
}

internal class Leaker
{
    private Timer t;
    public Leaker()
    {
        t = new Timer(callback);
        t.Change(1000, 0);
    }

    private void callback(object state)
    {
        System.Console.WriteLine("Still alive!");
        t.Change(1000, 0);
    }
}

GlaDOS非常相似,Leaker对象将无限期地“仍然活着” - 但是,无法访问对象(内部除外)以及对象在未被引用时如何知道了吗?)

答案 8 :(得分:1)

.NET确实有garbage collector来清理它。这就是它与其他非托管语言的区别。

但.NET可能会有内存泄漏。例如,GDI leaks在Windows窗体应用程序中很常见。我帮助开发的应用程序之一定期体验这一点。当办公室的员工整天使用它的多个实例时,他们达到Windows固有的10,000 GDI对象限制并不罕见。

答案 9 :(得分:1)

如果您没有引用使用 .NET的应用程序,这些答案很好地讨论,但实际上是指运行时本身,那么它在技术上可能会有内存泄漏,但此时垃圾收集器的实现可能几乎没有bug。我听说过一个案例,其中发现了一个错误,其中某些内容在运行时,或者可能只是在标准库中,有内存泄漏。但我不记得它是什么(一些非常模糊的东西),我不认为我能再找到它。

答案 10 :(得分:1)

.Net中不存在有效的C / C ++内存泄漏的一个主要来源是何时释放共享内存

以下内容来自Brad Abrams领导的关于设计.NET类库的课程

“嗯,第一点是,当然,没有内存泄漏,对吧?不是吗?还有内存泄漏?好吧,有一种不同的内存泄漏。那怎么样?那么那种内存我们没有的泄漏是,在过去的世界中,你习惯了一些记忆,然后忘记做一个免费或添加参考,忘记做一个释放,或者无论是什么配对。在新的世界里,垃圾收集器最终拥有所有内存,垃圾收集器将在没有任何引用的情况下释放这些内容。但是仍然存在泄漏,对吧?有什么类型的泄漏?好吧,如果你保留一个引用那个对象还活着,那么垃圾收集器就无法释放它。所以很多时候,你会发生什么事情,你认为你已经摆脱了整个对象图,但还是有一个人拿着它来引用它,然后你就被卡住了。垃圾收集器无法释放它,直到你放弃所有引用它。

我认为另一个是一个大问题。没有内存所有权问题。如果你去阅读WIN32 API文档,你会看到,好吧,我首先分配这个结构然后传入然后你填充它,然后我释放它。或者我告诉你大小并分配它然后我以后释放它或者你知道,所有这些争论都在谈论谁拥有那个记忆以及它应该被释放的地方。很多时候,开发人员只是放弃了这一点并说:“好吧,无论如何。好吧,当应用程序关闭时它将是免费的,“这不是一个好计划。

在我们的世界中,垃圾收集器拥有所有托管内存,因此没有内存所有权问题,无论您是创建它还是将其传递给应用程序,应用程序都会创建并开始使用它。任何一个都没有问题,因为没有歧义。垃圾收集器拥有它。 “

Full Transcript

答案 11 :(得分:1)

请记住,缓存和内存泄漏之间的区别在于策略。如果您的缓存有一个糟糕的策略(或者更糟,没有)用于删除对象,那么它与内存泄漏无法区分。

答案 12 :(得分:1)

此参考资料显示了如何使用弱事件模式在.Net中发生泄漏。 http://msdn.microsoft.com/en-us/library/aa970850.aspx

答案 13 :(得分:0)

.NET可能会出现内存泄漏,但它可以帮助您避免内存泄漏。所有引用类型对象都从托管堆中分配,托管堆跟踪当前正在使用的对象(值类型通常在堆栈上分配)。每当在.NET中创建新的引用类型对象时,都会从此托管堆中分配它。垃圾收集器负责定期运行和释放任何不再使用的对象(不再被应用程序中的任何其他对象引用)。​​

Jeffrey Richter的书CLR via C#对如何在.NET中管理内存有一个很好的章节。

答案 14 :(得分:0)

我发现的最好的例子实际上来自Java,但同样的原则适用于C#。

我们正在读取由许多长行组成的文本文件(每行在堆中几MB)。从每个文件中,我们搜索了几个关键子字符串并保留了子字符串。在处理了几百个文本文件后,我们的内存不足。

事实证明,string.substring(...)将保留对原始长字符串的引用...即使我们只保留1000个字符左右,这些子字符串仍将使用几MB内存。实际上,我们将每个文件的内容保存在内存中。

这是一个导致泄漏记忆的悬空参考的例子。 substring方法试图重用对象,但最终浪费了内存。

编辑:不确定这个特定问题是否困扰.NET。这个想法是为了说明在垃圾收集语言中执行的实际设计/优化,在大多数情况下,这种语言是智能且有用的,但可能导致不必要的内存使用。

答案 15 :(得分:0)

如果您使用托管dll但dll是否包含不安全的代码呢?我知道这是分裂的头发,但如果你没有源代码,那么从你的角度来看,你只是使用托管代码,但你仍然可以泄漏。