我如何编写一个unit testing来确定一个对象是否可以被垃圾收集?
关于我以前的问题 ,我需要检查一下,是否会被Castle Windsor实例化的组件可以在我的代码完成使用后被垃圾回收。 我已经在上一个问题的答案中尝试了这个build议,但至less对于我的代码来说,它似乎没有像预期的那样工作。 所以我想写一个unit testing,testing某个特定的对象实例是否可以在我的一些代码运行后被垃圾回收。
这可以做到可靠的方式吗?
编辑
根据Paul Stovell的回答,我目前有以下的testing成功:
[TestMethod] public void ReleaseTest() { WindsorContainer container = new WindsorContainer(); container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy(); container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient); Assert.AreEqual(0, ReleaseTester.refCount); var weakRef = new WeakReference(container.Resolve<ReleaseTester>()); Assert.AreEqual(1, ReleaseTester.refCount); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.AreEqual(0, ReleaseTester.refCount, "Component not released"); } private class ReleaseTester { public static int refCount = 0; public ReleaseTester() { refCount++; } ~ReleaseTester() { refCount--; } }
我是否正确地假设,基于上面的testing,我可以得出结论,当使用NoTrackingReleasePolicy时,Windsor不会泄漏内存?
这是我通常做的事情:
[Test] public void MyTest() { WeakReference reference; new Action(() => { var service = new Service(); // Do things with service that might cause a memory leak... reference = new WeakReference(service, true); })(); // Service should have gone out of scope about now, // so the garbage collector can clean it up GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsNull(reference.Target); }
注意:在生产应用程序中,应该调用GC.Collect()的时间非常less。 但是testing泄漏是合适的一个例子。
也许你可以持有一个WeakReference ,然后在testing完成后检查它是否已经存在(即!IsAlive)。
基于Paul的回答 ,我创build了一个更可重用的Assert方法。 由于string
的值被复制,我添加了一个明确的检查。 它们可以被垃圾收集器收集。
public static void IsGarbageCollected<TObject>( ref TObject @object ) where TObject : class { Action<TObject> emptyAction = o => { }; IsGarbageCollected( ref @object, emptyAction ); } public static void IsGarbageCollected<TObject>( ref TObject @object, Action<TObject> useObject ) where TObject : class { if ( typeof( TObject ) == typeof( string ) ) { // Strings are copied by value, and don't leak anyhow. return; } int generation = GC.GetGeneration( @object ); useObject( @object ); WeakReference reference = new WeakReference( @object, true ); @object = null; // The object should have gone out of scope about now, // so the garbage collector can clean it up. GC.Collect( generation, GCCollectionMode.Forced ); GC.WaitForPendingFinalizers(); Assert.IsNull( reference.Target ); }
以下unit testing显示该function在一些常见的情况下正在工作。
[TestMethod] public void IsGarbageCollectedTest() { // Empty object without any references which are held. object empty = new object(); AssertHelper.IsGarbageCollected( ref empty ); // Strings are copied by value, but are collectable! string @string = ""; AssertHelper.IsGarbageCollected( ref @string ); // Keep reference around. object hookedEvent = new object(); #pragma warning disable 168 object referenceCopy = hookedEvent; #pragma warning restore 168 AssertHelper.ThrowsException<AssertFailedException>( () => AssertHelper.IsGarbageCollected( ref hookedEvent ) ); GC.KeepAlive( referenceCopy ); // Still attached as event. Publisher publisher = new Publisher(); Subscriber subscriber = new Subscriber( publisher ); AssertHelper.ThrowsException<AssertFailedException>( () => AssertHelper.IsGarbageCollected( ref subscriber ) ); GC.KeepAlive( publisher ); }
由于使用Release
configuration(我假设编译器优化)的差异,如果GC.KeepAlive()
不被调用,这些unit testing中的一些将失败。
完整的源代码(包括一些辅助方法) 可以在我的库中find 。
使用dotMemory单元框架(它是免费的)
[TestMethod] public void ReleaseTest() { // arrange WindsorContainer container = new WindsorContainer(); container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy(); container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient); var target = container.Resolve<ReleaseTester>() // act target = null; // assert dotMemory.Check(memory => Assert.AreEqual( 0, memory.GetObjects(where => where.Type.Is<ReleaseTester>().ObjectsCount, "Component not released"); }
这不是一个答案,但是你可能想尝试在Debug和Release模式下运行你的代码(为了比较起见)。
根据我的经验,JIT代码的debugging版本更容易debugging,因此可能会看到引用保持更长的时间(相信function范围)。但是, 释放模式下的JIT代码可能会在快速删除后快速收集对象范围,如果集合发生。
也不回答你的问题:-)
我会有兴趣看到你在互操作模式(托pipe和本地)使用Visual Studiodebugging此代码,然后显示消息框或东西后,打破。 然后你可以打开Debug-> Windows-Immediate,然后键入
load sos (Change to thread 0) !dso !do <object> !gcroot <object> (and look for any roots)
(或者你可以使用Windbg作为其他人已经发布在以前的职位)
谢谢,亚伦