Page Comparison - Tutorial 2 - How to Find a Memory Leak with dotMemory (v.52 vs v.53)

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Redirect
visiblefalse
locationhttps://www.jetbrains.com/help/dotmemory/How_to_Find_a_Memory_Leak.html

Note
iconfalse

Disclaimer: This tutorial should not be treated as a universal guideline for locating and fixing memory leaks. It is only about giving you a feel of one of the possible dotMemory workflows.

...

Let’s try this tactic for fixing a leak in our sample application.

Contents

Sample App
Step 1. Run Profiler
Step 2. Get Snapshots
Step 3. Compare Snapshots
Step 4. Analyze Snapshot
Step 5. Check for Other Leaks

Sample App
Anchor
Sample App
Sample App

...

  1. Open the survived GameOfLife.AdWindow  object set consisting of two objects. To do this, click the number 2 in the Survived objects column next to the GameOfLife.AdWindow class.

    As the object exists in both snapshots, dotMemory will prompt you to specify in which snapshot the object should be shown. Of course, we’re interested in the last snapshot where the windows should have been collected.
  2. Select Open “Survived Objects” in the newer snapshot and click OK

    This will show the object set “All objects of the AdWindow class that exist both in snapshot #1 and #2” in the Type List view. According to the view, the object set contains 2 instances with the shallow size of 952 B. These instances exclusively retain other objects with the total size of 10,676 B.
    We’re interested not in the AdWindow objects themselves, but in those that retain our ad windows in memory. To figure this out, we should look at the selected object set using the Group by Dominators view. This will show us dominators---the objects that exclusively retain our ad windows in memory.
  3. To view the list of dominators for the object set, click Group by Dominators in the list of views.

    As you can see, ad windows are retained in memory by event handlers, which, in turn, are referenced by instances of the DispatcherTimer class. Let’s continue our investigation and try to find more details about those timer objects.
  4. Right click the DispatcherTimer object set in the list and select Open this object set.

    Note
    iconfalse

    * Formally speaking, dotMemory now shows you the object set “All objects of the DispatcherTimer class that dominate the object set “All objects of the AdWindow class…””. By the way, if you now look at Analysis Path, you’ll see the path of our investigation which starts from comparing two snapshots and ends with our suspect – Dominators “...DispatcherTimer”.

    This will open the DispatcherTimer object set* in the Type List view. Now, our goal is to understand how these timers relate to the AdWindow objects. In other words, how the timers reference our ad windows. To get this info, we should dive deeper and take a look at the specific instance of the DispatcherTimer class.

  5. Open the Instances view and double click any instance of the DispatcherTimer class. It doesn't really matter which one you choose, as they obviously have the same relationship with our ad windows. 

    By default, the instance is shown using the Outgoing References view. This view is used to get the details on the instance’s fields and references.
    As you remember, the ad windows are retained by event handlers, which, in turn, are referenced by the DispatcherTimer instances. The Outgoing References view shows how exactly this is happening---the ad window is referenced through the Tick event handler. It appears that the AdWindow instances are subscribed to the Tick event of the timers. Let’s look at this in the code.
  6. To quickly find the required call in the code, let’s use dotMemory. Simply switch to the Creation Stack Traces view.

    Here it is! The latest call in the stack that actually creates the timer is the AdWindow constructor. Let’s find it in the code. 
  7. Switch to Visual Studio with the GameOfLife solution and locate the AdWindow constructor.

    Code Block
    languagecsharp
    public AdWindow(Window owner)
    {
        ...
        adTimer = new DispatcherTimer();
        adTimer.Interval = TimeSpan.FromSeconds(3);
        adTimer.Tick += ChangeAds;
        adTimer.Start();
    }
    

    As you can see, our ad window uses the ChangeAds method to handle the event.
    But why is the ad window kept in memory after we close it? The thing is that we subscribed the window to the timer’s event but forgot to unsubscribe it. Therefore, the fix of this leak is quite simple: we need to add some Unsubscribe() method which should be called when closing the ad window. In fact, the code already contains such a method, and all you need to do is uncomment the Unsubscribe(); line in the window’s OnClosed event. Finally, the code should look like this:

    Code Block
    languagecsharp
    protected override void OnClosed(EventArgs e)
    {
        Unsubscribe();
        base.OnClosed(e);
    }
    
    public void Unsubscribe()
    {
        adTimer.Tick -= ChangeAds;
    }
    
  8. Now, to make sure the leak is fixed, let’s build our solution and run the profiling again. Click the Profile item in dotMemory’s menu and repeat Step 2. Get Snapshots and Step 3. Compare Snapshots

    That’s it! The AdWindow instances are now in the Dead objects column which means they were successfully collected by the time of getting the second snapshot. The leak is fixed!

...

  1. Open the second snapshot in dotMemory. To do this, click the GameOfLife.exe step (the beginning of your investigation) in Analysis Path and then click the Snapshot #2 link for the second snapshot.
  2. Open the Type List view for the snapshot by clicking the Largest Size link.
  3. In the opened Type List view, enter dispatchertimer in the filter field. This will narrow the list down, leaving only objects that contain this pattern in their class names. 

    As you can see, there are 8 DispatcherTimer objects in the heap.
  4. Open the DispatcherTimer object set by double clicking it.

    This will open the set in the Type List view. Now, we need to ensure that this set doesn’t contain the timers created by the ad windows. As the timers were created in the AdWindow constructor, the easiest way to do this is to look at the set using the Group by Creation Stack Trace view.
  5. Click the Back Traces link in the list of views (under Group by Creation Stack Trace).
    This will show us calls starting from the one that directly created the object, and descending to the first call in the stack.

    Unfortunately, the AdWindow.ctor(Window owner) call is still here, meaning that the timers created by this call were not collected. They exist in the snapshot regardless of the fact that the ad windows were closed and removed from memory. This looks like one more memory leak that we should analyze.
  6. Double click the AdWindow.ctor(Window owner) call.

    dotMemory will show us the object set (consisting of two timers) in the Type List view.
    To figure out what retains the timers in memory, let’s look at the Group by Dominators view.
  7. Click Group by Dominators in the list of views.
    The list of dominators contains just one row, Not exclusively retained objects, which means that each timer is retained in memory by more than one object.
    In such cases, the best solution is to look at main retention paths of such 'not exclusively retained' object. For this purpose, dotMemory has a view called Group by Similar Retention
  8. Click Group by Similar Retention in the list of views.

    The Group by Similar Retention view groups objects in a set by similarity of their retention paths. In addition, this view shows the two most dissimilar retention paths for each group. Typically, this is enough to understand what prevents your object from being collected. 
  9. Click any timer in the list.
    As you can see, our timers have slightly different retention paths. In fact, they differ only in one additional PriorityItem object; therefore, in our example there's no big difference which of the timer instances to analyze.
    The first retention path of our timers leads us to the DispatcherTimer list, which is global and stores all timers in the app. The second way shows that the timer is also retained by the DispatcherOperationCallback object. This object is a delegate that is created when you run the timer. This means that the timer is still running. One peculiar thing of the DispatcherTimer class is that the instance is removed from the global timer list only after the timer is stopped. Therefore, to fix the leak, we must stop the timer before the ad window is closed. Let’s do this in the code!
  10. Open the AdWindow.cs file which contains the implementation of the AdWindow class. Actually, the fix will be quite simple. All we need to do is add the adTimer.Stop(); line to the Unsubscribe() method. After the fix, the method should look like this:

    Code Block
    languagecsharp
    public void Unsubscribe()
    {
        adTimer.Tick -= ChangeAds;
        adTimer.Stop();
    }
    
  11. Rebuild the solution.
  12. Click the Profile item in the dotMemory’s menu and repeat Step 2. Get Snapshots and Step 3. Compare Snapshots.
  13. Open the second snapshot in the Type List view.

    As you can see, there are only 6 DispatcherTimer objects instead of 8 in the snapshot where we determined the leak. To ensure that GC collected the timers used by the ad windows, let’s look at these timers from the Group by Creation Stack Trace view.
  14. Double click the DispatcherTimer objects and then click the Back Traces link in the list of views. 

...