垃圾收集器哪些对象不清理?

时间:2010-11-23 21:16:50

标签: c# garbage-collection

静态分析工具一直告诉我,我的C#代码中有资源泄漏。

以下是一个例子:

StringReader reader = new StringReader(...); 

// do something with reader

...

} // static analysis tool cries that I've leaked **reader**

我的工具是否正确?如果是这样,为什么?

编辑(回复评论) - 我的静态分析工具说我有一堆资源泄漏。我从forum知道需要显式释放某些Java AWT对象,否则会发生泄漏。是否需要明确释放C#对象?

9 个答案:

答案 0 :(得分:13)

是的,您的代码泄漏严重。它应该是这样的:

using (StringReader reader = new StringReader(...))
{

}

每个实现IDisposable的类都需要包含在using block中,以确保始终调用Dispose方法。


更新:

Elaborating:在.NET中有IDisposable接口,它定义了Dispose方法。实现此接口的类(如文件流,数据库连接,读取器......)可能包含指向非托管资源的指针,并且确保释放这些非托管资源/句柄的唯一方法是调用Dispose方法。所以在.NET中确保即使抛出异常也会调用某些代码是使用try / finally语句:

var myRes = new MyResource(); // where MyResource implements IDisposable
try
{
    myRes.DoSomething(); // this might throw an exception
}
finally
{
    if (myRes != null)
    {
        ((IDisposable)myRes).Dispose();
    }
}

编写C#代码的人很快就意识到每次处理可处理资源时写这个都是PITA。所以他们引入了using声明:

using (var myRes = new MyResource())
{
    myRes.DoSomething(); // this might throw an exception
}

有点短。

答案 1 :(得分:6)

在这种特定情况下,您的代码实际没有泄漏任何内容,因为StringReader实际上没有任何资源可以清理,就像MemoryStream没有。 (对于MemoryStream,你 仍然需要处理它,如果你异步使用它或远程使用它......但在简单的情况下它并不重要。)

然而,在原则上处理任何实现IDisposable的东西是个好主意。这样可以避免泄漏(可能是暂时的)非托管资源,例如文件句柄。

例如,假设您将代码更改为:

StreamReader reader = new StreamReader("file.txt");
...

如果您未在此处关闭或处置reader(在finally块中或通过using statement),它将保持文件打开,直到任何类型直接保存操作系统文件句柄最终确定。明确地处理事物不仅可以更早地释放非托管资源 - 它还可以将最终化对象从终结队列中取出,这意味着它们可以在之前进行垃圾收集。

答案 2 :(得分:2)

看起来你没有丢弃你的StringReader。您需要调用.Dispose()来清理非托管资源。或者更好的是,在using块中使用它:

using (StringReader reader = new StringReader(...))
{
    // your code
}

这会在块结束时自动导致Dispose(),即使您的代码抛出异常(与使用finally块相同)。

答案 3 :(得分:2)

正如其他人所说,归结为StringReader没有被处理掉,所以我不打算这样做。

正在发生的是静态分析工具本质上是一个愚蠢的工具。我并不是说愚蠢,因为不使用它,我的意思是愚蠢,因为它正在寻找一个非常有限的标准。

在这种情况下,它会看到一个实例化的对象,其类实现了IDisposable。然后,该工具只是在对象超出范围之前查看是否进行了相应的dispose调用。这可以通过明确说出object.Dispose();或者通过使用(var x = ...){}子句。

根据MS specs,类应该在处理非托管资源(如文件句柄)时实现IDisposable。现在,您可能想要查看此MSDN post,其中讨论了实现IDisposable的哪些类,您不一定拥有来调用dispose()。

这给我们留下了两个可行的解决方案。第一个(也是我和Darin建议的那个)是总是在using子句中包装一个实现IDisposable的对象。这只是一种很好的做法。毕竟,它会导致零伤害,而没有它可能会导致大量内存泄漏(取决于类),我not smart enough要记住哪个是哪个。

另一种方法是配置静态分析工具(如果可能)以忽略此类警告。我真的认为这将是一个坏主意(tm)

答案 4 :(得分:2)

有许多类的流,具有相关类型的阅读器对象。其中一些类以一种必须在完全放弃之前撤消的方式操纵外部对象,但在仍然需要时无法撤消(例如文件读取器将打开文件;文件必须在文件句柄之前关闭被遗忘了,但在读者完成之前不能关闭)。 “Dispose”方法将处理读者在完全放弃之前必须“退回”的任何事情。因为一段代码创建一个流式读取器并将其移交给另一段代码是很常见的,并且因为第一段代码可能无法知道使用读取器的代码何时完成,代码使用阅读器有责任在阅读器完成后调用Dispose。

请注意,某些类型的读者实际上并没有在他们的Dispose方法中执行任何操作,但任何接受任意类型的流读取器的代码都应该调用它。虽然您的代码需要一种不需要dispose的流式读取器,但可能会给它一个需要它的派生类。即使在没有严格必要的情况下调用Dispose也可以防止出现这种情况。

答案 5 :(得分:1)

我相信这是因为你完成它之后没有调用Close方法,尽管根据MSDN:

  

Close的这个实现调用Dispose方法传递一个真值。

所以我希望当垃圾收集器到达阅读器时,它将被处置为具有相同的最终结果(并且没有内存泄漏)。

更新:我错了,GC确实自动处理IDisposable对象。您需要明确调用Close(或Dispose)。

答案 6 :(得分:0)

你已经泄露,但“最终”GC会为你清理它。

答案 7 :(得分:0)

字符串阅读器可能正在访问流。通过不丢弃stringreader,您可能会打开该流。流可以附加到系统上的文件中,然后您可能已将其锁定。

尝试查看using语句,并自动调用dispose。

using (sr) {
 // your code
}

答案 8 :(得分:0)

垃圾收集器将收集任何不再引用它的内容。在您的示例中,最终将收集reader(尽管没有人能分辨出来)。

但是,“静态分析工具”抱怨您没有手动拨打Dispose()

StringReader reader = ...
...
reader.Dispose();

在这个具体案例中,这可能不是什么大问题。但是,在处理许多IO类(* Stream,* Reader等)时,最好在完成后处理它们。您可以使用using来提供帮助:

using(StringReader reader = ...) {
    ...
}  //reader is automatically disposed here
相关问题