C# 垃圾收集器不清理哪些对象?
一个静态分析工具不断告诉我,我的C#代码中存在资源泄漏 下面是一个例子:C# 垃圾收集器不清理哪些对象?,c#,garbage-collection,C#,Garbage Collection,一个静态分析工具不断告诉我,我的C#代码中存在资源泄漏 下面是一个例子: StringReader reader = new StringReader(...); // do something with reader ... } // static analysis tool cries that I've leaked **reader** 我的工具正确吗?若然,原因为何 编辑(回复评论)-我的静态分析工具说我有一大堆资源泄漏。我从中了解到,某些JavaAWT对象需要显式释放,否则
StringReader reader = new StringReader(...);
// do something with reader
...
} // static analysis tool cries that I've leaked **reader**
我的工具正确吗?若然,原因为何
编辑(回复评论)-我的静态分析工具说我有一大堆资源泄漏。我从中了解到,某些JavaAWT对象需要显式释放,否则会发生泄漏。是否有需要显式释放的C#对象?您已经泄漏,但“最终”GC将为您清理。我相信这是因为您在完成
关闭方法后没有调用它,尽管根据MSDN:
Close的这个实现调用Dispose方法,并传递一个真值
因此,我希望当垃圾收集器到达读卡器时,它将得到相同的最终结果(并且没有内存泄漏)
更新:我错了,GC不会自动处理IDisposable对象。您需要显式调用Close(或Dispose)。是的,您的代码严重泄漏。应该是这样的:
using (StringReader reader = new StringReader(...))
{
}
实现IDisposable
的每个类都需要包装在一个包中,以确保始终调用Dispose
方法
更新:
详细说明:在.NET中有一个定义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();
}
}
using (var myRes = new MyResource())
{
myRes.DoSomething(); // this might throw an exception
}
编写C#代码的人很快就意识到,每次处理一次性资源时编写C#代码都是一个难题。因此,他们使用
语句引入了:
var myRes = new MyResource(); // where MyResource implements IDisposable
try
{
myRes.DoSomething(); // this might throw an exception
}
finally
{
if (myRes != null)
{
((IDisposable)myRes).Dispose();
}
}
using (var myRes = new MyResource())
{
myRes.DoSomething(); // this might throw an exception
}
有点短。stringreader可能正在访问流。通过不处理stringreader,您可能会使该流保持打开状态。流可以附加到系统上的一个文件,而您可能已经锁定了它
尝试查看using语句,它将自动为您调用dispose
using (sr) {
// your code
}
看起来您没有处理StringReader。您需要调用.Dispose()
来清理非托管资源。或者更好的方法是,在中使用块:
using (StringReader reader = new StringReader(...))
{
// your code
}
这将导致Dispose()自动位于块的末尾,即使代码抛出异常(与使用finally块相同)。垃圾收集器将收集不再引用它的任何内容。在您的示例中,reader
最终将被收集(尽管没有人知道何时)
但是,“静态分析工具”抱怨您没有手动调用Dispose()
在这种情况下,这可能没什么大不了的。然而,当处理许多IO类(*流、*读卡器等)时,最好在完成后处理它们。您可以使用使用来帮助:
using(StringReader reader = ...) {
...
} //reader is automatically disposed here
在这种情况下,您的代码实际上没有泄漏任何内容,因为StringReader
实际上没有任何资源来清理,就像MemoryStream
没有一样。(使用MemoryStream
时,如果异步使用或远程处理它,您仍然可能需要处理它……但在简单的情况下,这并不重要。)
然而,原则上处理任何实现了IDisposable
的东西是个好主意。这将避免泄漏(可能是暂时的)非托管资源,如文件句柄
例如,假设您将代码更改为:
StreamReader reader = new StreamReader("file.txt");
...
如果您不在此处(在finally
块中或通过a)关闭或处置读卡器
,它将保持文件打开,直到直接保持OS文件句柄的任何类型完成。显式处理东西不仅可以更早地释放非托管资源,还可以将可终结对象从终结队列中移除,这意味着可以更早地对它们进行垃圾收集。正如其他人所说,这归结为没有处理StringReader,所以我不想再提这一点
现在的情况是,静态分析工具本质上是一个愚蠢的工具。我的意思不是“哑巴”,因为“不要使用它”,我的意思是“哑巴”,因为它是在一个非常有限的标准上
在本例中,它看到一个对象被实例化,其类实现了IDisposable。然后,该工具只是查看在对象超出范围之前是否进行了相应的dispose调用。这可以通过明确说出object.Dispose()来实现;或者通过using(var x=…){}子句
根据,类在处理非托管资源(如文件句柄)时应实现IDisposable。现在,您可能想回顾一下这篇文章,它讨论了哪些类实现了IDisposable,而您不必调用dispose()
这给我们留下了两个可行的解决方案。第一个(也是我和Darin建议的)是始终将实现IDisposable的对象包装在using子句中。这只是一种很好的练习。毕竟,它不会造成任何伤害,而没有它可能会导致大量内存泄漏(取决于类),我需要记住哪个是哪个
另一种方法是配置静态分析工具(如果可能的话)以忽略此类警告。我真的认为这是一个坏主意(tm)有很多类的流,以及相关类型的读卡器对象。其中一些类操作外部对象的方式必须在完全放弃它们之前撤消,但在仍然需要它们时不能撤消(例如,文件读取器将打开文件;在忘记文件句柄之前必须关闭文件,但不能关闭)