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

时间:2022-09-07 23:58:12

In relation to my previous question, I need to check whether a component that will be instantiated by Castle Windsor, can be garbage collected after my code has finished using it. I have tried the suggestion in the answers from the previous question, but it does not seem to work as expected, at least for my code. So I would like to write a unit test that tests whether a specific object instance can be garbage collected after some of my code has run.

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

Is that possible to do in a reliable way ?

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

EDIT

编辑

I currently have the following test based on Paul Stovell's answer, which succeeds:

我目前根据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--;
        }
    }

Am I right assuming that, based on the test above, I can conclude that Windsor will not leak memory when using the NoTrackingReleasePolicy ?

我是否正确地假设,基于上述测试,我可以得出结论,使用NoTrackingReleasePolicy时Windsor不会泄漏内存?

5 个解决方案

#1


78  

This is what I normally do:

这就是我通常做的事情:

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

NB: There are very, very few times where you should call GC.Collect() in a production application. But testing for leaks is one example of where it's appropriate.

注意:很少有人应该在生产应用程序中调用GC.Collect()。但是测试泄漏是适当的一个例子。

#2


4  

Perhaps you could hold a WeakReference to it and then check to see that it no longer alive (i.e., !IsAlive) after the tests have completed.

也许你可以持有WeakReference,然后在测试完成后检查它是否已经不再存在(即!IsAlive)。

#3


3  

Based on Paul's answer, I created a more reusable Assert method. Since string's are copied by value I added an explicit check for them. They can be collected by the garbage collector.

根据Paul的回答,我创建了一个更可重用的Assert方法。由于字符串是按值复制的,因此我添加了对它们的显式检查。它们可以被垃圾收集器收集。

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

The following unit tests show the function is working in some common scenarios.

以下单元测试显示该功能在一些常见场景中有效。

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

Due to differences when using the Release configuration (I assume compiler optimizations), some of these unit tests would fail if GC.KeepAlive() were not to be called.

由于使用Release配置时的差异(我假设编译器优化),如果不调用GC.KeepAlive(),这些单元测试中的一些将失败。

Complete source code (including some of the helper methods used) can be found in my library.

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

#4


2  

Use dotMemory Unit framework (it's free)

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

#5


1  

This is not an answer, however you may want to try running your code in both Debug and Release modes (for comparison sake).

这不是一个答案,但您可能希望尝试在调试和发布模式下运行代码(为了比较)。

In my experience the Debug version of JIT'ed code is made easier to debug and thus may see references stay alive longer (I belive function scope) However, code JITed in Release mode may have the objects ready for collection quickly once it is out of scope and if a Collection happens.

根据我的经验,JIT代码的调试版本更容易调试,因此可能会看到引用保持更长时间(我相信功能范围)但是,在发布模式下JITed的代码可能会让对象准备好在收集后快速收集范围和收集是否发生。

Also not answering your question: :-)
I would be interested in seeing you debug this code using Visual Studio in Interop mode (Managed and Native) and then breaking after displaying a message box or something. Then you can open the Debug->Windows-Immediate and then type

也没有回答你的问题::-)我有兴趣看到你在Interop模式下使用Visual Studio调试此代码(Managed and Native),然后在显示消息框之后断开。然后你可以打开Debug-> Windows-Immediate然后输入

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

(or you can use Windbg as other's have posted in previous posts)

(或者您可以像以前的帖子中发布的那样使用Windbg)

Thanks, Aaron

谢谢,亚伦

#1


78  

This is what I normally do:

这就是我通常做的事情:

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

NB: There are very, very few times where you should call GC.Collect() in a production application. But testing for leaks is one example of where it's appropriate.

注意:很少有人应该在生产应用程序中调用GC.Collect()。但是测试泄漏是适当的一个例子。

#2


4  

Perhaps you could hold a WeakReference to it and then check to see that it no longer alive (i.e., !IsAlive) after the tests have completed.

也许你可以持有WeakReference,然后在测试完成后检查它是否已经不再存在(即!IsAlive)。

#3


3  

Based on Paul's answer, I created a more reusable Assert method. Since string's are copied by value I added an explicit check for them. They can be collected by the garbage collector.

根据Paul的回答,我创建了一个更可重用的Assert方法。由于字符串是按值复制的,因此我添加了对它们的显式检查。它们可以被垃圾收集器收集。

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

The following unit tests show the function is working in some common scenarios.

以下单元测试显示该功能在一些常见场景中有效。

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

Due to differences when using the Release configuration (I assume compiler optimizations), some of these unit tests would fail if GC.KeepAlive() were not to be called.

由于使用Release配置时的差异(我假设编译器优化),如果不调用GC.KeepAlive(),这些单元测试中的一些将失败。

Complete source code (including some of the helper methods used) can be found in my library.

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

#4


2  

Use dotMemory Unit framework (it's free)

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

#5


1  

This is not an answer, however you may want to try running your code in both Debug and Release modes (for comparison sake).

这不是一个答案,但您可能希望尝试在调试和发布模式下运行代码(为了比较)。

In my experience the Debug version of JIT'ed code is made easier to debug and thus may see references stay alive longer (I belive function scope) However, code JITed in Release mode may have the objects ready for collection quickly once it is out of scope and if a Collection happens.

根据我的经验,JIT代码的调试版本更容易调试,因此可能会看到引用保持更长时间(我相信功能范围)但是,在发布模式下JITed的代码可能会让对象准备好在收集后快速收集范围和收集是否发生。

Also not answering your question: :-)
I would be interested in seeing you debug this code using Visual Studio in Interop mode (Managed and Native) and then breaking after displaying a message box or something. Then you can open the Debug->Windows-Immediate and then type

也没有回答你的问题::-)我有兴趣看到你在Interop模式下使用Visual Studio调试此代码(Managed and Native),然后在显示消息框之后断开。然后你可以打开Debug-> Windows-Immediate然后输入

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

(or you can use Windbg as other's have posted in previous posts)

(或者您可以像以前的帖子中发布的那样使用Windbg)

Thanks, Aaron

谢谢,亚伦