重现步骤:
这是我的代码:
using (TestClass test = new TestClass())
{
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
这就是我定义TestClass的方法:
public class TestClass : IDisposable
{
public void Dispose()
{
Dispose(true);
//GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Console.WriteLine("Disposing TestClass.");
}
}
~TestClass()
{
Dispose(false);
Console.WriteLine("TestClass Finalizer called");
}
}
预期行为: 我确实看到“Disposing TestClass”在using语句之后按预期打印,但我也希望在运行GC命令后打印“TestClass Finalizer called”。我确保我跳过调用GC.SuppressFinalize(this);在Dispose方法中。看似已处置的变量即使在超出范围之后也无法最终确定。它们似乎在程序退出之前就已经完成了。
实际行为 我只看到在使用语句之后打印“Disposing TestClass”,只是在GC命令之后才看到“TestClass Finalizer被调用”。我只是在程序退出之前才看到它。
这不是内存泄漏吗?
如果我将它转换为非一次性类并更新如下代码,我确实看到在GC命令之后调用终结器。
TestClass test = new TestClass();
test = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
public class TestClass
{
~TestClass()
{
Console.WriteLine("TestClass Finalizer called");
}
}
答案 0 :(得分:4)
根据Microsoft对Object.Finalize Method ()
终结器执行的确切时间未定义。要确保为类的实例确定性地释放资源,请实现Close方法或提供IDisposable.Dispose实现。
将终结者视为第二道防线。如果程序未能拨打Close
或Dispose
,则终结器将进行更改以更正遗漏。
这仍然可以确保文件在程序退出时关闭,例如,除非另一个终结器没有退出,因此阻止其他终结器或者如果灾难性异常残忍地终止该程序。
这不是内存泄漏。如果内存变得稀缺,垃圾收集器(GC)可以决定在程序退出之前很久就释放资源并调用终结器。
答案 1 :(得分:2)
调用Dispose
方法不会影响对象的运行时。 GC仅收集不再引用的对象(例外情况适用)。在您的第一个示例中,test
变量永远不会被垃圾收集(通过您的GC.Collect
语句),因为它在包含方法内部而不是在使用块的范围内声明。
给出以下C#输入(A
实施IDisposable
):
using (var a = new A())
{
}
发出以下IL代码:
IL_0001: newobj instance void Testit.A::.ctor()
IL_0006: stloc.0 // a
.try
{
// [10 13 - 10 14]
IL_0007: nop
// [12 13 - 12 14]
IL_0008: nop
IL_0009: leave.s IL_0016
} // end of .try
finally
{
IL_000b: ldloc.0 // a
IL_000c: brfalse.s IL_0015
IL_000e: ldloc.0 // a
IL_000f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0014: nop
IL_0015: endfinally
} // end of finally
如果我们将它转换为C#,那将是〜结果:
A a;
try
{
}
catch
{
if (a != null)
{
a.Dispose();
}
}
变量a
具有方法范围,因此仅在保留方法时才收集。这就是没有调用终结器的原因。
在第二个示例中,您故意将实例设置为null
从范围中删除实例,因为没有活动指针可以收集实例。
答案 2 :(得分:0)
我想尽管你把测试变量设置为null仍然有一个对它的引用,它在调试模式下的某些.NET版本上发生。 试试这段代码
private void CreateAndRelease()
{
new TestClass();
}
public void Main()
{
CreateAndRelease();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
请注意,GC.WaitForPendingFinalizers被调用两次 - 首先GC.Collect不会最终确定你的对象,而只是将它放入终结队列,只有在下一次垃圾收集时才能完成它。
修改强> 一些害羞的人低估了我的答案,但我发布之前检查了它不像他们。 .NET 4.5.1调试模式
代码和输出1 :
Output:
Disposing TestClass.
Finished
public static void Main()
{
using (TestClass test = new TestClass())
{}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Finished");
Console.ReadLine();
}
代码和输出2 :
Disposing TestClass.
TestClass Finalizer called
Finished
public static void Main()
{
CreateAndRelease();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Finished");
Console.ReadLine();
}
private static void CreateAndRelease()
{
using (TestClass test = new TestClass())
{}
}
所以我的答案绝对正确,并将有助于主题启动。
答案 3 :(得分:0)
以下是此行为的原因:
以下代码:
label1
获取编译为:
using (TestClass test = new TestClass())
{
}
出于与Strange behaviour while Unit testing for memory leak using WeakReference中所述相同的原因,当您在调试模式下运行时,"测试"变量是有效的,直到方法结束,因此它不是GC,并且终结器没有被调用。如果您在发布模式下运行相同的东西,它将按预期工作。
对于非一次性类型(在我的第二部分中),因为有一种方法可以指定这个" test"变量为null,因此GC即使在调试模式下使用也可以收集它,因此它可以按预期工作。