繁体   English   中英

如何编写单元测试以确定是否可以垃圾回收对象?

[英]How can I write a unit test to determine whether an object can be garbage collected?

关于我的上一个问题 ,我需要检查在我的代码使用完之后是否可以将由Castle Windsor实例化的组件进行垃圾回收。 我已经在上一个问题的答案中尝试了该建议,但是它似乎并没有按预期工作,至少对于我的代码而言。 因此,我想编写一个单元测试,以测试我的某些代码运行后是否可以对特定的对象实例进行垃圾回收。

这可能以可靠的方式进行吗?

编辑

目前,我根据Paul Stovell的回答进行了以下测试,该测试成功了:

     [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--;
        }
    }

我是否可以基于上述测试得出结论,即使用NoTrackingReleasePolicy可以得出温莎不会泄漏内存的结论?

这是我通常所做的:

[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()。 但是,测试泄漏是适当情况下的一个例子。

也许您可以对它保留一个WeakReference ,然后在测试完成后检查它是否不再有效(即,!IsAlive)。

根据Paul的回答 ,我创建了一种更可重用的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 );
}

以下单元测试表明该功能在某些常见情况下有效。

[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配置时存在差异(我假设是编译器优化),因此,如果不调用GC.KeepAlive() ,则其中一些单元测试将失败。

完整的源代码(包括一些使用的辅助方法) 可以在我的库中找到

使用dotMemory Unit框架(免费)

[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");
}

这不是答案,但是您可能希望尝试在DebugRelease模式下运行代码(出于比较目的)。

以我的经验,JIT版本的Debug版本更易于调试,因此可能会看到引用的生存期更长(我相信函数的作用域)。但是,在Release模式下JITed的代码一旦离开了对象,就可以快速地准备好收集对象。范围以及是否发生收集。

也没有回答您的问题::-)
我希望看到您在互操作模式(托管和本机)下使用Visual Studio调试此代码,然后在显示消息框或其他内容后中断代码。 然后,您可以打开Debug-> Windows-Immediate,然后键入

load sos
(Change to thread 0)
!dso
!do <object>
!gcroot <object> (and look for any roots)

(或者您可以使用Windbg,就像其他人在以前的帖子中所发表的)

谢谢亚伦

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM