当在Debug配置中构建或附带调试器时,似乎某些更新改变了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)
,在<gcConcurrent enabled="false"/>
和App.config
中设置GCSettings.LatencyMode = GCLatencyMode.Batch
- 没有任何帮助。如果我在没有附加调试器的情况下运行代码并且它是在Release配置(即优化)中构建的 - 它输出Dead
。否则为Alive
。
据我所知,依赖GC并不是一个好主意。但是对于测试我不知道如何更换检查特定代码片段不会泄漏内存的能力。这是纯粹的测试组件,我可以转动一些兼容性开关,或类似的东西。我的目标是检查自己的代码,而不是GC优化。
有没有办法以某种方式强制GC进行以前的行为?
P.S。我看到几乎相同的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清除其目标,以及为什么将它放在隐藏在调用堆栈中的范围内也不会这样做。
答案 0 :(得分:1)
在调试模式下,这应该使所有.NET版本中的对象保持活动状态。其他任何东西都是错误(或缺失的功能)。
您可以通过将一些代码拆分为新方法来禁用此调试辅助工具。
在释放模式下,这应该表现出您想要的短GC寿命。当然不能保证这一点,但这是一个非常理想的优化。
另一种解决方法是使用new object[1]
或类似的构造。然后,您可以可靠地清空第一个阵列成员。我认为该框架具有Box
或StrongBox
类型。不知道它叫什么。
答案 1 :(得分:1)
您无法真正依赖IsAlive属性。它的问题在于,只有在返回false时才能信任它。
Have a look at Why You Shouldn’t Rely On WeakReference.IsAlive
WeakReference指向一个实时的对象 (可到达),或尚未收集的垃圾(无法到达) 通过GC,IsAlive属性将返回true。在一个对象之后 如果WeakReference很短(或者如果是目标对象),则收集 没有终结器)然后IsAlive将返回false。 不幸的是,当IsAlive返回时,目标可能已经存在 集。
由于GC的方式,可能会发生这种情况 在扫描堆垃圾之前挂起所有托管线程 收集它(这是一个过于简化的解释说明 目的)。 GC可以在两条指令之间的任何时间运行。
以下方法是检查它的可靠方法。
object a = new object();
WeakReference wr = new WeakReference(a);
object aa = (object)wr.Target;
Console.WriteLine(aa != null ? "Alive" : "Dead");
答案 2 :(得分:0)
最简单的方法是使var a = new object();
成为类字段而不是局部变量。使测试不那么孤立,但现在似乎是一致的,并且不会阻止GC在方法的中间收集。