我想验证设置WeakReference
的代码是否意外地保留了对引用对象的强引用。 (这里是an example如何轻易意外地做到这一点。)
这看起来是检查无意中强引用的最佳方法吗?
TestObject testObj = new TestObject();
WeakReference wr = new WeakReference(testObj);
// Verify that the WeakReference actually points to the intended object instance.
Assert.Equals(wr.Target, testObject);
// Force disposal of testObj;
testObj = null;
GC.Collect();
// If no strong references are left to the wr.Target, wr.IsAlive will return false.
Assert.False(wr.IsAlive);
答案 0 :(得分:12)
我与微软就这一点取得联系,并了解/确认:
GC.Collect()
强制阻止垃圾回收。GC.Collect()
运行时,它不会神秘地跳过符合条件的集合对象。遵循可预测的规则来确定要收集的对象。只要您了解这些规则(即如何处理最终化的对象),您就可以强制销毁某个特定对象,尽管被破坏对象使用的内存可能会被释放,也可能不被释放。有关我博客的更多信息:Can .Net garbage collection be forced?
答案 1 :(得分:4)
我昨天就这样做了。这是我必须添加的内容,以确保在上一次断言之前发生了收集:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
如果在此之后.IsAlive仍然是真的,可能在某处仍然存在强烈的引用。
顺便说一下 - 当您访问WeakReference目标时,请务必不要检查.IsAlive。为了避免在检查.IsAlive和.Target之间出现竞争条件,请执行以下操作:
var r = weakRef.Target AS Something;
if (r != null)
{
... do your thing
}
答案 2 :(得分:1)
涉及WeakReference
对象的单元测试比您预期的要棘手。正如您和其他人所指出的,GC.Collect()
可以“强制”垃圾收集,但这仍然取决于您的对象没有引用。
不幸的是,构建代码的方法可以更改对象是否仍然具有对它们的引用。更具体地说,当对象仍然植根时,无论是在调试模式下还是在释放模式下进行构建,都会发生改变(准确地说,取决于您是否打开了优化功能;调试默认情况下将其关闭,而发布版本则将其默认打开) 。调试模式关闭了许多优化,甚至倾向于将当前正在执行的方法中创建/声明的对象植根。因此,您的单元测试在Debug版本中可能会失败,而在Release版本中会成功。
在您的示例中,即使您将testObj
设置为NULL,编译器仍将通过保持其先前值为根来尝试对Debug构建产生帮助。这意味着无论您调用GC.Collect()
多少次,wr.IsAlive
都将始终返回TRUE。
那么,您如何测试WeakReference
?很简单:使用另一种方法创建它们以及它们所基于的对象。只要该方法没有内联,并且在大多数情况下不会,编译器就不会将您关心的对象作为根,并且您可以使测试通过Debug和Release版本。
以下功能为您提供了有关如何执行此操作的提示:
public static Tuple<WeakReference, ManualResetEvent, int> GetKillableWr(Func<object> func, bool useGetHashCode = false)
{
var foo = func();
var result = new Tuple<WeakReference, ManualResetEvent, int>(new WeakReference(foo), new ManualResetEvent(false), useGetHashCode ? (foo?.GetHashCode() ?? 0) : RuntimeHelpers.GetHashCode(foo));
Task.Factory.StartNew(() =>
{
result.Item2.WaitOne();
GC.KeepAlive(foo); // need this here to make sure it doesn't get GC-ed ahead of time
foo = null;
});
return result;
}
使用此方法,只要在func
参数内创建对象 ,就可以为您的对象创建WeakReference
选择在您返回返回的ManualResetEvent
并调用GC.Collect()
之后将不会被植根。正如其他人指出的那样,调用下面的代码以确保在需要时进行清理会很有帮助...
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
编辑:
还有其他一些“陷阱”需要担心。一个常见的涉及String
个。 String
文字和常量始终植根,因为它们被编译为对DLL / EXE的引用。因此,new WeakReference("foo")
之类的东西总是会显示为活动状态,因为“ foo”已存储在您的DLL中,并且在编译后的代码中提供了对该存储文字的引用。解决此问题的一种简单方法是使用new StringBuilder("<your string here>").ToString()
代替字符串文字。
再次编辑:
另一个“陷阱”是,在Release版本中,优化导致GC更具攻击性,这与上述情况不同,可能导致对象超出预期范围的超出范围。在下面的代码中,wr.IsAlive
有时可以返回FALSE,因为GC已检测到myObject
不会被该方法中的其他任何东西使用,因此使其有资格进行垃圾回收。解决方法是将GC.KeepAlive(myObject)
放在方法的末尾。这将使myObject
扎根,直到至少执行该行。
public static void SomeTest()
{
var myObject = new object();
var wr = new WeakReference(myObject);
GC.Collect();
Assert.True(wr.IsAlive, "This could fail in Release Mode!");
}