垃圾收集应该已经删除了对象,但WeakReference.IsAlive仍然返回true
我有一个测试,我希望通过,但垃圾收集器的行为不是我所假设的:
[Test] public void WeakReferenceTest2() { var obj = new object(); var wRef = new WeakReference(obj); wRef.IsAlive.Should().BeTrue(); //passes GC.Collect(); wRef.IsAlive.Should().BeTrue(); //passes obj = null; GC.Collect(); wRef.IsAlive.Should().BeFalse(); //fails }
在这个例子中, obj
对象应该是GC’d,因此我希望WeakReference.IsAlive
属性返回false
。
似乎因为obj
变量声明在与GC.Collect
相同的范围内,所以它没有被收集。 如果我在方法之外移动obj声明和初始化,则测试通过。
有没有人对此行为有任何技术参考文档或解释?
遇到与你相同的问题 – 我的测试到处传递,除了在NCrunch下(可能是你的情况下的任何其他仪器)。 嗯。 使用SOS进行调试后,在测试方法的调用堆栈上显示了其他根。 我的猜测是,它们是代码检测的结果,它禁用了任何编译器优化,包括那些正确计算对象可达性的优化。
这里的解决方法非常简单 – 不要从GC和测试活力的方法中获得强有力的参考 。 这可以通过简单的辅助方法轻松实现。 下面的更改使您的测试用例通过了NCrunch,它最初失败了。
[TestMethod] public void WeakReferenceTest2() { var wRef2 = CallInItsOwnScope(() => { var obj = new object(); var wRef = new WeakReference(obj); wRef.IsAlive.Should().BeTrue(); //passes GC.Collect(); wRef.IsAlive.Should().BeTrue(); //passes return wRef; }); GC.Collect(); wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes } private T CallInItsOwnScope(Func getter) { return getter(); }
我可以看到一些潜在的问题:
-
我不知道C#规范中的任何内容都要求局部变量的生命周期是有限的。 在非调试版本中,我认为编译器可以自由地省略
obj
的最后一个赋值(将其设置为null
),因为没有代码路径会导致obj
的值永远不会在它之后使用,但我希望在非调试构建元数据将指示在创建弱引用之后永远不会使用该变量。 在调试版本中,变量应该存在于整个函数范围内,但是obj = null;
声明应该实际清除它。 尽管如此,我不确定C#规范承诺编译器不会省略最后一个语句但仍保留变量。 -
如果您使用的是并发垃圾收集器,那么可能是
GC.Collect()
触发了集合的立即启动,但是在GC.Collect()
返回之前该集合实际上不会完成。 在这种情况下,可能没有必要等待所有终结器运行,因此GC.WaitForPendingFinalizers()
可能过度,但它可能会解决问题。 -
当使用标准垃圾收集器时,我不希望存在对象的弱引用以终结器的方式延长对象的存在,但是当使用并发垃圾收集器时,它可能被放弃的对象存在弱引用被移动到具有需要清理的弱引用的对象队列,并且这种清理的处理发生在与其他所有内容同时运行的单独线程上。 在这种情况下,需要调用
GC.WaitForPendingFinalizers()
来实现所需的行为。
请注意,通常不应期望弱引用将在任何特定程度的及时性时失效,也不应期望在IsAlive
报告为true之后获取Target
将产生非空引用。 人们应该只在人们不关心目标的情况下使用IsAlive
,如果它还活着,但是有兴趣知道引用已经死亡。 例如,如果有一个WeakReference
对象的集合,则可能希望定期遍历列表并删除目标已经死亡的WeakReference
对象。 应该准备好WeakReferences
可能在集合中保留的时间超过理想情况所需的时间; 如果他们这样做的唯一后果应该是轻微浪费内存和CPU时间。
据我所知,调用Collect
并不能保证所有资源都被释放。 你只是向垃圾收集器提出建议。
您可以尝试强制它阻止,直到通过执行以下操作释放所有对象:
GC.Collect(2, GCCollectionMode.Forced, true);
我希望这可能在100%的时间都不会起作用。 一般来说,我会避免编写任何依赖于观察垃圾收集器的代码,它实际上并不是设计用于这种方式。
可能是.Should()
扩展方法以某种方式挂在引用上吗? 或者测试框架的某些其他方面可能导致此问题。
(我发布这个作为答案,否则我不能轻易发布代码!)
我尝试了以下代码,它按预期工作(Visual Studio 2012,.Net 4构建,调试和发布,32位和64位,在Windows 7上运行,四核处理器):
using System; namespace Demo { internal class Program { private static void Main(string[] args) { var obj = new object(); var wRef = new WeakReference(obj); GC.Collect(); obj = null; GC.Collect(); Console.WriteLine(wRef.IsAlive); // Prints false. Console.ReadKey(); } } }
尝试此代码时会发生什么?
我有一种感觉,你需要调用GC.WaitForPendingFinalizers(),因为我期望终结器线程更新周引用。
多年前我编写unit testing并回想起WaitForPendingFinalizers()
帮助时遇到了问题,因此调用了GC.Collect()
。
该软件从未在现实生活中泄露过,但是编写一个unit testing来certificate该对象不能保持活着比我希望的要困难得多。 (过去我们的缓存存在错误,确实让它保持活力。)