Identifying Memory Leaks with dotnet-dump and dotnet-gcdump

Swift Kim

Engineer

In this tutorial, you will learn how to monitor memory usage of a Tizen .NET application and identify possible memory leaks using dotnet-dump and dotnet-gcdump.

Note: The total memory usage of an application process is affected by various factors (for example, shared size, swap size, or memory allocated by the runtime for its internal use). Only the managed memory region (GC heap) can be analyzed with dotnet-dump and dotnet-gcdump.

Prerequisites

You need the following things installed on your device:

Monitoring memory usage

First, obtain the process ID of the currently running application process. Use dotnet dump ps to list all .NET processes running on the device.

sh-3.2$ dotnet counters ps
     17876 dotnet-loader /usr/bin/dotnet-loader
     17464 LeakMemory.dll /usr/bin/dotnet-loader

To examine the current memory usage of the target process, use dotnet counters monitor.

sh-3.2$ dotnet counters monitor -p 17464
Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    % Time in GC since last GC (%)                         0
    Allocation Rate / 1 sec (B)                       56,992
    CPU Usage (%)                                         29
    Exception Count / 1 sec                                0
    GC Heap Size (MB)                                     34
    Gen 0 GC Count / 60 sec                                0
    Gen 0 Size (B)                                        12
    Gen 1 GC Count / 60 sec                                0
    Gen 1 Size (B)                                    13,820
    Gen 2 GC Count / 60 sec                                0
    Gen 2 Size (B)                                   493,068
    LOH Size (B)                                  33,587,792
    Monitor Lock Contention Count / 1 sec                  0
    Number of Active Timers                                3
    Number of Assemblies Loaded                           52
    ThreadPool Completed Work Item Count / 1 sec          61
    ThreadPool Queue Length                                0
    ThreadPool Thread Count                                2
    Working Set (MB)                                      89

In this tutorial, we are only interested with the managed heap size (GC Heap Size) of the process. (cf. Working Set indicates the total bytes of memory used by the process.) If the heap size unexpectedly grows over time, it is likely that your application has a memory leak.

Analyzing managed heap dump with dotnet-dump

To capture a memory dump (coredump) from the target process, use dotnet dump collect.

sh-3.2$ dotnet dump collect -p 17464 -o /home/owner/share/coredump.17464
Writing full to /home/owner/share/coredump.17464
Complete

Make sure the target process has write access to the specified path. Also ensure that there's enough space available on the disk (df -h /opt/usr) because the size of the dump is often very large (200+ MB). Otherwise, you will see a file write error (HRESULT: 0x80004005).

To analyze the generated coredump, use dotnet dump analyze.

sh-3.2$ dotnet dump analyze /home/owner/share/coredump.17464
Loading core dump: /home/owner/share/coredump.17464 ...
Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.
Type 'quit' or 'exit' to exit the session.
>

You can now type any SOS command in the command prompt. For example, clrstack -all provides stack traces of all managed threads.

If we want to find managed objects within the GC heap that caused a memory leak, the dumpheap -stat command provides the overall statistics about the managed heap.

> dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
...
f026e634       60        35556 System.Object[]
f026f3b8       30        46038 System.Char[]
f026fc3c      245        51356 System.Int32[]
f0290744     1531       133922 System.String
e3706968        2     16777240 Xamarin.Forms.ContentPage[]

The last line reveals that the memory leak is caused by Xamarin.Forms.ContentPage[] type objects. Use the gcroot command to print the chain of references that are keeping those objects alive.

> dumpheap -type Xamarin.Forms.ContentPage[]
 Address       MT     Size
f126391c e38cad40       12
d9c82010 e38cad40 33554444

Statistics:
      MT    Count    TotalSize Class Name
e38cad40        2     33554456 Xamarin.Forms.ContentPage[]
Total 2 objects

> gcroot d9c82010
Thread 4438:
    FF8BF398 F7023CD0 <unknown>
            ...
            ->  F12637B0 LeakMemory.Views.CircularButtonPage
            ->  F1263908 System.Collections.Generic.List`1[[Xamarin.Forms.ContentPage, Xamarin.Forms.Core]]
            ->  D9C82010 Xamarin.Forms.ContentPage[]

Found 1 unique roots (run 'gcroot -all' to see all roots).

As shown, the leaky objects are held by a LeakMemory.Views.CircularButtonPage type object.

This is the general procedure of identifying major sources of memory leaks in your application. You can continue investigating the coredump using various SOS commands and options to find other underlying issues in your code.

Analyzing managed heap dump with dotnet-gcdump

An alternative to dotnet-dump is dotnet-gcdump which also allows you to track object allocation and memory leaks from a running application process. GC dumps are often easier to use than coredumps because they are portable (cross-platform format) and lightweight (very small size). Find more information about GC dumps at Collecting and analyzing memory dumps.

To capture a GC dump (snapshot) from the target process, use dotnet gcdump collect. You can take multiple snapshots from the same process while it's running.

sh-3.2$ dotnet gcdump collect -p 17464 -o /home/owner/share/17464.gcdump
Writing gcdump to '/home/owner/share/17464.gcdump'...
        Finished writing 11101371 bytes.

Using Visual Studio

You can use Visual Studio to open and analyze the generated GC snapshot. Copy the .gcdump file from the device to the host by typing sdb pull /home/owner/share/17464.gcdump and open it in Visual Studio.

Visual Studio

By sorting the objects by their types and sizes, List<Xamarin.Forms.ContentPage> appears at the top of the table, which is the main source of the memory leak. The chain of references is also shown in the bottom panel (Paths to Root).

If you have more than one snapshot, compare two different snapshots using diff to see which objects grow fast over time. Choose a snapshot that you want to serve as the baseline in the Compare to: section.

Visual Studio comparison view

Using PerfView

If the information obtained above is not enough for you, you can switch to another tool called PerfView. At first glance, this tool doesn't look very intuitive, but is extremely helpful for performing complex analysis of managed heap data.

Tip: PerfView also supports the trace file format (.nettrace) used by the dotnet-trace tool (see Finding Performance Bottlenecks with dotnet-trace). You can specify detailed provider keywords to capture certain event information (for example, JIT or GC) when recording trace data.

PerfView

PerfView FlameGraph

By switching between a set of different views and filtering necessary data, you can get detailed information about managed objects in the GC heap. Pressing F1 anywhere opens a pop-up window with the help page for the current view. Also read the following pages for further details of the PerfView tool. Note that some parts of the documents are outdated or not generally applicable for Tizen (PerfView had been Windows-only for a long time before .NET Core was released in 2016).