When a .NET application starts consuming excessive memory, becomes unresponsive, or crashes intermittently, logs alone may not reveal the cause. The application might be retaining objects longer than expected, blocking threads, running into an out-of-memory condition, or failing inside a specific execution path.

A memory dump captures a snapshot of a process at a particular point in time. Developers can inspect that snapshot later to investigate the state of the application without attaching an interactive debugger to the live production service.

For C# applications, the dotnet-dump tool provides a practical way to collect and analyze managed process dumps across supported operating systems. This article explains how to capture dumps, inspect managed memory, identify retained objects, and apply the findings to production troubleshooting.

What Is a .NET Memory Dump?

A memory dump is a file containing diagnostic information about a running process. Depending on the dump type, it can include thread stacks, exception information, managed heap objects, runtime data, and other process memory.

For example, consider an ASP.NET Core API whose memory usage increases after every batch of requests. The application might be retaining customer records in a static collection or accumulating entries in an application cache without an eviction policy.

A memory dump can help answer questions such as:

A dump is a snapshot, not a complete history. It shows the application's state at collection time, so comparing multiple snapshots can be more informative than inspecting only one.

Install the dotnet-dump Tool

Install the .NET diagnostic tool with the following command:

dotnet tool install --global dotnet-dump

If the tool is already installed, update it when appropriate:

dotnet tool update --global dotnet-dump

Verify the installation:

dotnet-dump --version

The tool supports collecting and analyzing dumps on Windows, Linux, and macOS, subject to the supported runtime and platform requirements. It provides managed-runtime diagnostics through SOS commands, but it is not a full native debugger.

For containerized applications, ensure the tool is available in the environment where collection will occur and that it can access the target process.

Collect a Memory Dump from a Running C# Application

First, identify the process ID of the application.

On Linux, you can inspect running processes with:

ps -ef | grep dotnet

For a container, identify the process from inside the relevant container or use your platform's process-inspection tools.

Once you have the process ID, collect a dump:

dotnet-dump collect \
  --process-id 1234 \
  --output /tmp/api-memory.dmp

Replace 1234 with the actual process ID. Choose an output directory with sufficient free disk space and appropriate permissions.

On Windows, the equivalent command can use a Windows path:

dotnet-dump collect `
  --process-id 1234 `
  --output C:\Dumps\api-memory.dmp

By default, dotnet-dump collect creates a full dump when no dump type is specified. You can explicitly select a type using --type.

Choose the Appropriate Dump Type

Dump type

Typical use

Mini

Initial investigation when thread stacks, module information, and exception details may be sufficient

Heap

Managed-memory investigations that need heap objects and related process information

Full

More comprehensive analysis when a broader process-memory snapshot is necessary

Triage

Smaller diagnostic capture with personal identifiable information removed

The appropriate choice depends on the investigation and the diagnostic information required. A smaller dump may not contain enough information to explain a memory-retention problem.

For example, a managed memory investigation often benefits from a heap dump:

dotnet-dump collect \
  --process-id 1234 \
  --type Heap \
  --output /tmp/api-heap.dmp

Production caution: Collecting a full or heap dump can temporarily increase memory pressure, consume substantial disk space, and affect application responsiveness. In a memory-constrained container, collection itself can contribute to the operating system terminating the process. Test your collection procedure before relying on it during an incident.

Analyze the Dump with SOS Commands

After collecting the dump, open it in the interactive analysis shell:

dotnet-dump analyze /tmp/api-heap.dmp

The shell accepts SOS diagnostic commands. These commands let you inspect the managed heap, threads, runtime structures, and references between objects.

Start by listing the available commands:

help

Then investigate the symptom rather than running every command indiscriminately.

Find the Largest Managed Object Types

For suspected memory growth, begin with heap statistics:

dumpheap -stat

This command summarizes managed objects by type, including instance counts and total size.

You might find that a particular application type has hundreds of thousands of instances, or that large numbers of strings and byte arrays dominate the heap.

For example, suppose the output identifies a type named MyApp.Caching.CustomerRecord with an unexpectedly large total size. That observation gives you a concrete starting point.

Filter the heap statistics by a type name:

dumpheap -type CustomerRecord -stat

You can also inspect individual objects of that type:

dumpheap -type CustomerRecord

The output includes object addresses. Select an address from the actual output before passing it to another command.

For example:

dumpobj 00007f6ad09421f8

Replace the example address with the address reported by your dump. dumpobj displays the object's type, size, and fields that can help you understand what the object contains.

Remember that the largest type is not automatically a memory leak. A large heap may be legitimate for a busy service. The important question is whether the observed object count and retained memory are consistent with expected application behavior.

Identify Why Objects Remain in Memory

The garbage collector reclaims managed objects when they are no longer reachable through live references. An object can remain in memory even when application code no longer intends to use it, provided something still references it.

Common causes include:

After finding a suspicious object, use gcroot to investigate its references:

gcroot 00007f6ad09421f8

The address must be replaced with the address of the object you want to inspect.

The command attempts to show the reference paths that keep the object reachable. For example, a path might lead from a static field to a cache, then to a collection containing thousands of customer records.

That result gives you a more useful question to investigate in source code: why is the cache retaining these records, and what lifecycle or eviction rule should release them?

gcroot can take time to complete, especially for large heaps. Interpret its output carefully; a reference path identifies reachability, but it does not by itself prove that the reference is a defect.

Inspect Managed Threads and Stacks

A process can consume excessive resources because of blocked threads or synchronization problems rather than a conventional memory leak.

List managed threads:

threads

Inspect managed stack traces:

clrstack -all

Look for repeated call stacks, unexpected blocking, or many threads waiting in the same code path.

For additional thread-pool information, use:

threadpool

If the issue involves locks, syncblk can provide information about synchronization blocks and lock ownership:

syncblk

These commands help build a picture of the application's execution state. However, a dump is only a snapshot. A thread that appears blocked at capture time might not remain blocked afterward, and a single snapshot may not establish the cause of a deadlock.

Compare Dumps to Confirm Memory Growth

One snapshot shows what the process retained at a particular moment. Two or more snapshots can reveal how that state changes over time.

Use this workflow:

  1. Collect a baseline dump when memory usage is relatively normal.

  2. Continue observing the application under representative traffic.

  3. Collect another dump after memory usage increases.

  4. Run dumpheap -stat on both dumps.

  5. Compare instance counts and total sizes for suspicious object types.

  6. Use gcroot to investigate objects that remain unexpectedly reachable.

For example, if the number of CustomerRecord objects increases substantially between snapshots, examine whether requests are adding records to a long-lived collection without removing them.

Make sure the snapshots are comparable. Different request volumes, cache warm-up, workload composition, and garbage collection timing can all affect heap statistics. A growing object count is evidence to investigate, not definitive proof of a leak.

Memory Dumps Versus GC Dumps and Traces

.NET provides several diagnostic tools, and choosing the correct one matters.

Diagnostic artifact

Best suited for

Process memory dump

Inspecting process state, managed objects, thread stacks, and reference paths

GC dump from dotnet-gcdump

Examining managed heap type statistics and object relationships

EventPipe trace

Investigating runtime activity over time, including performance and garbage collection behavior

A GC dump is not interchangeable with a full process dump. The dotnet-gcdump tool walks the managed heap and triggers a generation 2 garbage collection to collect its data. On a large heap, this can suspend the runtime for a significant period, so evaluate its impact before using it in a performance-sensitive production service.

When you need to understand why memory is retained, a process dump with suitable heap information is often a strong starting point. When you need to understand how allocations or garbage collections evolve over time, a trace may provide evidence that a single snapshot cannot.

Production Security and Operational Considerations

Memory dumps may contain application data, including credentials, access tokens, connection strings, personal information, request payloads, and other sensitive values.

Treat dump files as sensitive production artifacts.

Also verify disk capacity and container memory limits before collection. If a production process is already near its memory limit, a large dump operation can worsen the incident.

For services with multiple replicas, remote collection, or recurring diagnostic needs, evaluate dotnet-monitor and your platform's observability tooling rather than relying exclusively on manual shell access.

Common Mistakes to Avoid

Collecting a dump without a hypothesis: Start with a symptom such as sustained memory growth, a blocked request path, or repeated allocation failures. This helps determine the appropriate dump type and analysis commands.

Assuming high memory usage means a leak: The garbage collector may legitimately retain objects, and the application may need memory for its workload. Compare snapshots and investigate reference paths before changing code.

Choosing a dump that lacks the required information: A small dump may help with thread stacks but may not support the heap analysis you need. Select the type according to the investigation.

Ignoring the impact of collection: Dump generation can consume disk space and affect memory-constrained processes. Test the procedure under realistic conditions.

Treating object references as proof of a defect: A reference chain explains why an object remains reachable. You still need to determine whether that lifetime is intended.

Summary

Memory dumps provide a practical way to investigate difficult C# production failures when logs and metrics are not enough. With dotnet-dump, you can capture a process snapshot, inspect managed heap statistics, trace object references, and examine managed thread stacks.

Start with dumpheap -stat for memory growth, use gcroot to investigate suspiciously retained objects, and inspect thread stacks when the problem involves blocking or unresponsiveness. Compare multiple dumps to distinguish expected workload behavior from persistent growth.

Most importantly, treat dump collection as an operational and security-sensitive activity. Select the appropriate dump type, account for resource overhead, and protect the resulting files as carefully as any other production data.