Search Results for

    Show / Hide Table of Contents

    Use Assertions to Detect Memory Leaks

    Another part of the API are the assertions, which can be used to detect potential memory leaks.

    In many cases, it is possible to know how an operation will affect the memory usage of the managed heap when it is executed. For instance, some operations should not create new live instances of certain classes after it has finished. By using the assertion methods in the MemAssertion class you can make sure that the memory usage of the operation behaves as expected. There are two ways of performing memory assertions:

    • Defining the assertions in an AssertionsDefinition instance and then perform the assertions using the MemAssertion.Assert method.

    • Using the NoInstances, NoNewInstances and NoNewInstancesExcept methods in the MemAssertion class.

    The AssertionsDefinitions approach requires slightly more coding, but it is much more flexible and provides additional memory checks, so it is the recommended approach when performing memory assertions.

    To perform a memory assertion, you have to perform the following:

    1. Establish a base snapshot using the method MemProfiler.FastSnapshot().
      The base snapshot is used as a reference when looking for new instances.

    2. Perform the operation that you want to check for memory leaks.

    3. Analyze the memory impact of the operation and write memory assertions that checks that instances are garbage collected as expected.

    To catch as many memory leaks as possible, it is desirable to include memory assertions that check for as many instances as possible. The problem is that many operations may have side effects on memory that are not easy to anticipate. For instance, when performing an operation for the first time, new Types might get loaded, creating unexpected new instances of RuntimeTypes and Strings. It is therefore important to remember that the instances that are identified as memory leaks are identified only as potential memory leaks. The potential memory leak instances have to be analyzed to decide whether they are real leaks or if they are identified falsely as memory leaks by an incorrect memory assertion.

    The risk of falsely identifying instances as leaks is bigger when using the MemAssertion.NoNewInstancesExcept methods (or the MemAssertion.NoNewInstances and AssertionsDefinition.NoNewInstances methods without the Type argument), since these methods include all loaded types in the process, except the ones that have been specifically selected for exclusion. If these methods are used, do not perform the assertions the first time the operation is performed, so that side effects, e.g., from loading types, can be avoided.

    Example:

    Consider the ShowDialog method below, which shows a modal dialog (derived from System.Windows.Forms.Form):

    void ShowDialog()
    {
        using( SomeDialog dlg = new SomeDialog() )
        {
           dlg.ShowDialog();
        }
    }
    

    Creating a dialog, showing it, and then disposing it, are operations that should not create any new live instances. In reality, however, quite a few new live instances might be created as a side effect of creating the dialog. The first time the method is executed the SomeDialog type might get loaded, creating a new live Type instance (actually a RuntimeType), and new strings (e.g., the name of the type).

    In this case, it is known that after the dialog has been shown and disposed, it should be eligible for garbage collection. If the SomeDialog instance cannot be garbage collected, then we might have something referencing the instance unintentionally (for instance, a left-over event handler).

    To assert that the SomeDialog instance can be garbage collected, the NoNewInstances method can be used:

    MemAssertion.NoNewInstances( typeof( SomeDialog ) );
    

    This method will make a full garbage collect and then assert that no new instances of the SomeDialog class exist on the GC heap.

    The ShowDialog code with the memory assertion looks like this:

    using SciTech.NetMemProfiler;
    
    void ShowDialog()
    {
        // Establish a base snapshot
        MemProfiler.FastSnapshot();
       
        using( SomeDialog dlg = new SomeDialog() )
        {
           dlg.ShowDialog();
        }  
        
        // Assert that no new instances of SomeDialog has been
        // created. The FastSnapshot collected at the
        // beginning of the method will be used as
        // reference.
        MemAssertion.NoNewInstances( typeof( SomeDialog ) );
    }
    

    If the memory assertion fails, i.e., a new instance of SomeDialog does exist, a full snapshot will be collected and reported to the profiler, and the instance of SomeDialog will be marked as a potential memory leak.

    One problem with the example above is that it only detects instances of SomeDialog as potential memory leaks. The SomeDialog class probably contains a set of child Controls, and all of them should also be eligible for garbage collection after the dialog has been disposed. All those instances can also be checked by changing the NoNewInstances assertion to:

    MemAssertion.NoNewInstances( typeof( Control ), true );
    

    The second argument to NoNewInstances is a Boolean value indicating whether subclasses of the specified Type should be checked as well.

    Note

    When profiling a debug build of a program, the scope of the local variables is often longer than it might appear. In the example above, the dlg variable might still be in use at the time of the assertion. This will prevent the SomeDialog instance from being collected, and the assertion will fail, falsely identifying the SomeDialog instance as a memory leak.

    To avoid this, use a release build or wrap the tested code in a method:

    using SciTech.NetMemProfiler;
    
    void DoShowDialog()
    {
        using( SomeDialog dlg = new SomeDialog() )
        {
           dlg.ShowDialog();
        }
    }
    
    public void ShowDialog()
    {
        // Establish a base snapshot
        MemProfiler.FastSnapshot();
       
        DoShowDialog();  
    
        // Assert that no new instances of SomeDialog has been
        // created. The FastSnapshot collected at the
        // beginning of the method will be used as
        // reference.
        MemAssertion.NoNewInstances( typeof( SomeDialog ) );
    }
    
    In This Article
    Back to top

    © Copyright 2002-2020. SciTech Software AB.
    For information about .NET Memory Profiler, see the product site at https://memprofiler.com