为什么我的C#方法有用户对象的内存泄漏?

Eta*_*vad 1 .net c#

我一直在研究从数据库中提取大量记录的数据导出程序.其中一个步骤涉及将RTF文本字符串转换为纯文本,这最终导致用户对象在运行时发生内存泄漏.任务管理器将显示的一个列是"USER对象" - 当达到~10,000时,程序将耗尽分配空间,程序将出现"错误创建窗口句柄"

发生这种情况是因为我没有在方法结束时处理我的对象.

我的问题是,为什么C#/.net不为我处理它?

这是一个快速重现漏洞的代码示例.将代码放入Winforms应用程序并按下按钮以使其循环通过内存浪费.

private void wasteMemory()
{
    System.Windows.Forms.RichTextBox rtfBox = new System.Windows.Forms.RichTextBox();

    //RTF text that reads "Hello World"
    rtfBox.Rtf = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}  {\\colortbl ;\\red0\\green0\\blue0;}  \\viewkind4\\uc1\\pard\\cf1\\fs29 Hello World} ";

    //If line below is commented out, User Objects grow out of control.
    //rtfBox.Dispose();
}

private void button1_Click(object sender, EventArgs e)
{
    for (int i = 1; i < 100000; i++)
    {            
        wasteMemory();
    }
}
Run Code Online (Sandbox Code Playgroud)

我的理解是,当方法完成时,处理在其侧面创建的任何对象的方法范围.我期望rtfBox被处理掉,但事实并非如此.

Jon*_*son 6

Dispose方法是.NET方式,为具有本机资源的对象提供清理的机会.它有点像C++中的析构函数/删除 - 虽然不是真的.如果你没有在实现IDisposable的对象上调用Dispose,那就是一个bug,很可能会导致内存泄漏.最好做以下事情:

using(System.Windows.Forms.RichTextBox rtfBox = new System.Windows.Forms.RichTextBox())
{

  //RTF text that reads "Hello World"
  rtfBox.Rtf = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}  {\\colortbl ;\\red0\\green0\\blue0;}  \\viewkind4\\uc1\\pard\\cf1\\fs29 Hello World} ";
}
Run Code Online (Sandbox Code Playgroud)

使用块的行为与您期望的完全相同.您可以将其视为C++中用于堆栈上对象的方法范围.

我的理解是,当方法完成时,处理在其侧面创建的任何对象的方法范围.我期望rtfBox被处理掉,但事实并非如此.

不,这根本不是真的 - 或者与大多数其他垃圾收集的语言有关.如果您认识到这里的对象是动态分配的(即非常像指针),那么对于像C++这样的语言来说甚至都不是这样,因为当指针超出范围时,动态分配的内存不会被清除:你有显式调用delete.在.NET中,对象将被最终化,析构函数将被调用,并且当垃圾收集器到达它时将调用dispose,但在此之前不会.超出范围只会向垃圾收集器发出信号,表明相关对象有资格被收集.但是,任何具有资源的东西,例如本机代码,文件句柄或其他IDisposable实现对象,都应该通过处理来处理.

有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.idisposable.aspx


Eri*_*sch 6

到目前为止,每个答案都是不完整的 是的,确实必须清理非托管资源,但实现IDisposable的类已经这样做了.这不是重点.

在正确实现IDisposable的类中,如果没有显式或隐式地处理对象,那么它将在垃圾收集的终结器阶段处理.但是,当对象超出范围时,此过程不会立即发生.gc可能需要几分钟甚至几小时才能运行.

这里的问题是,如果你不自己调用Dispose()(或通过将它包装在using语句中隐式调用Dispose()),那么(如果它由类正确实现),该对象将被丢弃,直到垃圾收集器运行,这可能需要相当长的时间.

这意味着在垃圾回收器处理未引用的对象之前,您可能会耗尽非托管资源.这正是你遇到的问题.

自己调用Dispose()可以确保非完整对象在完成后立即得到清理,而不是在GC到达时.

把它想象成一个图书馆.有人检查了一本书,书架上有5本.当其他人查看这个图书馆时,有些人会把它们归还..但是他们不会立即放在架子上,他们会坐在回收箱里,直到有人到处检查并重新安置它们.

调用Dispose就像将书交给图书管理员,让他们立即检查,并将其放回架子上,以便下一个人可以到达它.

  • @BrianRasmussen - StreamWriter不是Stream,这是一个不同的问题.StreamWriter没有非托管资源.Stream确实如此,Stream确实有终结器. (2认同)