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:
Which managed object types consume the most memory?
Why are objects remaining reachable instead of being collected?
What were managed threads executing when the dump was captured?
Are threads blocked while waiting for locks?
Is the application experiencing a garbage collection or allocation problem?
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-dumpIf the tool is already installed, update it when appropriate:
dotnet tool update --global dotnet-dumpVerify the installation:
dotnet-dump --versionThe 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 dotnetFor 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.dmpReplace 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.dmpBy 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 |
|---|---|
| Initial investigation when thread stacks, module information, and exception details may be sufficient |
| Managed-memory investigations that need heap objects and related process information |
| More comprehensive analysis when a broader process-memory snapshot is necessary |
| 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.dmpProduction 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.dmpThe 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:
helpThen investigate the symptom rather than running every command indiscriminately.
Find the Largest Managed Object Types
For suspected memory growth, begin with heap statistics:
dumpheap -statThis 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 -statYou can also inspect individual objects of that type:
dumpheap -type CustomerRecordThe output includes object addresses. Select an address from the actual output before passing it to another command.
For example:
dumpobj 00007f6ad09421f8Replace 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:
Static collections that continue accumulating entries.
Caches without expiration or size limits.
Event subscriptions that keep subscribers alive.
Long-lived services retaining request-scoped data.
Background tasks or queues retaining large payloads.
Objects referenced by active threads or asynchronous state machines.
After finding a suspicious object, use gcroot to investigate its references:
gcroot 00007f6ad09421f8The 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:
threadsInspect managed stack traces:
clrstack -allLook for repeated call stacks, unexpected blocking, or many threads waiting in the same code path.
For additional thread-pool information, use:
threadpoolIf the issue involves locks, syncblk can provide information about synchronization blocks and lock ownership:
syncblkThese 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:
Collect a baseline dump when memory usage is relatively normal.
Continue observing the application under representative traffic.
Collect another dump after memory usage increases.
Run
dumpheap -staton both dumps.Compare instance counts and total sizes for suspicious object types.
Use
gcrootto 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 | 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.
Store dumps only in approved locations with restricted access.
Encrypt them at rest and during transfer where supported by your environment.
Avoid uploading them to public issue trackers or unapproved analysis services.
Define retention and deletion policies.
Record who collected and accessed each dump.
Ensure collection procedures comply with your organization's security requirements.
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.

Join the conversation! Your thoughts help the community grow.