【发布时间】:2015-09-30 22:35:50
【问题描述】:
当内置调试配置或附加调试器时,似乎一些更新改变了 GC 行为:
//Code snippet 1
var a = new object();
var w = new WeakReference(a);
a = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w.IsAlive ? "Alive" : "Dead");
这样的代码用于打印Dead,并且对于编写单元测试检查是否应该被GCed的某些部分没有被持有非常方便。
在 .NET 4.x 更新后,此代码在 .NET 2.x 和 3.x 上成功通过,但在 4.x 的所有变体上都失败。我试图将其称为GC.Collect(2, GCCollectionMode.Forced, blocking: true),在App.config 和GCSettings.LatencyMode = GCLatencyMode.Batch 中创建<gcConcurrent enabled="false"/> - 没有任何帮助。如果我在没有附加调试器的情况下运行代码并且它是在发布配置中构建的(即带有优化) - 它输出Dead。否则为Alive。
我知道在生产环境中依赖 GC 并不是一个好主意。但是对于测试,我不知道如何替换通过测试检查特定代码段不会泄漏内存的能力。这是纯粹的测试组装,我可以打开一些兼容性开关或类似的东西。我的目标是检查我自己的代码,而不是 GC 优化。
有没有办法以某种方式强制 GC 执行先前的行为?
附:我看到几乎相同的question,但当时它与NCrunch有关。我没有安装它。我什至从命令行运行代码,根本没有 VS,结果相同。
UPD:我发现,如果我将分配和设置对 null 的引用的代码移动到单独的方法中 - 但它始终输出 Dead。
//Code snippet 2
internal class Program
{
private static void Main(string[] args)
{
var w = DoWorkAndGetWeakRef();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w.IsAlive ? "Alive" : "Dead");
Console.ReadLine();
}
private static WeakReference DoWorkAndGetWeakRef()
{
var a = new object();
var w = new WeakReference(a);
a = null;
return w;
}
}
如果我转移到单独的方法 GC 收集调用和 WeakReference 检查,结果相同:
//Code snippet 3
internal class Program
{
private static void Main(string[] args)
{
var a = new object();
var w = new WeakReference(a);
a = null;
CollectAndCheckWeakRef(w);
Console.ReadLine();
}
private static void CollectAndCheckWeakRef(WeakReference w1)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w1.IsAlive ? "Alive" : "Dead");
}
}
尽管看起来很重要的一点是原始w 变量不在当前范围内。如果我将Console.WriteLine(w1.IsAlive ? "Alive" : "Dead"); 移回Main - 它再次变为Alive。
这两种变体有时不是很方便,但至少是一致的(Debug 或 Release 配置,调试器是否附加 - 仍然输出 Dead)。
现在我很好奇在当前执行范围内仅仅存在 WeakReference 变量是如何阻止 GC 清理其目标的,以及为什么将它放在埋在调用堆栈中的某个范围内的某个位置并不会起到同样的作用。
【问题讨论】:
-
@HansPassant 我知道 GC 能够收集未使用的局部变量,即使该方法仍在运行。这里的问题是为什么即使引用为空并明确要求它也不收集它?我已经构建了没有最新更新的代理,测试正在使用它并且已经通过了几年。在我的本地机器和新的构建代理上,由于某种原因,它确实失败了。
-
Hmya,专注于发布为每个人打印“Alive”的复制代码。
-
@HansPassant here 是在安装了 VS2015 的 Win8.1/Win10 上打印
Alive的二进制文件,在单独安装了 VS2010/VS2013 和 4.5.1 的 WinServer2008R2 上打印Dead。建立在 Win8.1/VS2015 的调试配置中,来自有问题的源代码。除了最后的Console.ReadLine()。 (我不知道如何准确检查 .NET 版本 - 有了所有这些就地更新,这并不容易......你能指导我去哪里看吗?) -
嗯,StrongBox,把它隐藏在你的 sn-p 中做得很好。那有什么意义呢?
-
@HansPassant 哎呀,抱歉,这是@usr 建议的带有
StrongBox<T>的版本——虽然没有改变,但为了完整起见here 也是原来的版本。跨度>
标签: c# .net garbage-collection