<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey" -->

---
title: Exploring .NET managed heap with ClrMD | daily.dev
description: A practical walkthrough of using ClrMD (Microsoft.Diagnostics.Runtime) to inspect the .NET managed heap by attaching to a live process or crash dump. Covers...
canonical: https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Exploring .NET managed heap with ClrMD | daily.dev
og:description: A practical walkthrough of using ClrMD (Microsoft.Diagnostics.Runtime) to inspect the .NET managed heap by attaching to a live process or crash dump. Covers...
og:url: https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey
og:image: https://api.daily.dev/og/posts/zvYQnUXEY.png
og:image:alt: Exploring .NET managed heap with ClrMD
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Exploring .NET managed heap with ClrMD

**[Maarten Balliauw](https://daily.dev/sources/maartenballiauw)** · 13 min read · 0 upvotes · 0 comments

## Summary

A practical walkthrough of using ClrMD (Microsoft.Diagnostics.Runtime) to inspect the .NET managed heap by attaching to a live process or crash dump. Covers attaching with DataTarget, reading CLR and AppDomain info, enumerating threads and stack traces, and exploring heap internals such as value types versus reference types. Two hands-on examples are built: finding the top 5 duplicated strings on the heap, and replicating dotMemory's retention path feature by walking GC roots down to a leaking Clock object held alive by a Timer event handler.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://blog.maartenballiauw.be/posts/2017-01-03-exploring-net-managed-heap-with-clrmd>

## Questions this post answers

### How can I find out what object is keeping another object alive in memory in a .NET application?

Use ClrMD to enumerate all GC roots with ClrHeap.EnumerateRoots, then walk down each root's reference tree using EnumerateRefsOfObject until the target object's address is found, recording the path on a stack. This replicates dotMemory's retention path feature without needing a commercial profiler, by recursively tracking visited objects to avoid cycles and printing the chain of addresses, type names, and sizes once the target object is located.

_daily.dev surfaces practical heap-walking techniques for developers chasing down .NET memory leaks._

### How do I get the top 5 most duplicated strings on the .NET heap using ClrMD?

Call ClrRuntime.GetHeap() and check CanWalkHeap, then enumerate object addresses with EnumerateObjectAddresses, filtering to objects whose ClrType.IsString is true, reading each value with type.GetValue(ptr), and tallying occurrences in a dictionary before ordering descending by count and taking the top 5. This mirrors the duplicate-string inspection found in JetBrains dotMemory.

_daily.dev helps developers comparing heap-analysis approaches like ClrMD versus commercial profilers._

### Why does a Timer not let a disposed object be garbage collected in .NET?

A System.Threading.Timer keeps its callback delegate referenced through its Tick event even after the target object's Dispose method has been called, so the object remains reachable from a GC root and cannot be collected. In a concrete example, a disposed Clock object stays alive because the Timer still holds a reference to its OnTick method, creating a memory leak until the event handler is explicitly detached.

_daily.dev keeps developers chasing subtle garbage collection and memory leak patterns like this one._

## Similar posts on daily.dev

- [Creating a memory dump in C\#](https://daily.dev/posts/creating-a-memory-dump-in-c--torykruun) · .NET Blog · 16 upvotes · 0 comments
- [Today I will… debug a production crash](https://daily.dev/posts/today-i-will-debug-a-production-crash-oix9ikedw) · Visual Studio Blog · 11 upvotes · 1 comments
- [The Fasting Developer: Memory Discipline and GC Awareness in .NET](https://daily.dev/posts/the-fasting-developer-memory-discipline-and-gc-awareness-in-net-5yq85h552) · We Are .NET · 12 upvotes · 0 comments
- [Exploring the .NET boot process via host tracing](https://daily.dev/posts/exploring-the-net-boot-process-via-host-tracing-nva4pcq2n) · .NET Escapades · 0 upvotes · 0 comments

---

Tags: [#.net](https://daily.dev/tags/.net), [#c#](https://daily.dev/tags/c#)

[View this post on daily.dev](https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Exploring .NET managed heap with ClrMD","url":"https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey"},"datePublished":"2026-10-04T13:04:16.763Z","dateModified":"2026-10-04T13:05:36.772Z","description":"A practical walkthrough of using ClrMD (Microsoft.Diagnostics.Runtime) to inspect the .NET managed heap by attaching to a live process or crash dump. Covers...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/398114bdd900640afce10c4cb3215ca6?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/398114bdd900640afce10c4cb3215ca6?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Maarten Balliauw","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Maarten Balliauw","logo":"https://media.daily.dev/image/upload/s--rOTg3VYO--/f_auto,q_auto/v1791119043/logos/maartenballiauw?_a=BAMAMicg0","url":"https://daily.dev/sources/maartenballiauw"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":".net,c#","timeRequired":"PT13M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Maarten Balliauw","item":"https://daily.dev/sources/maartenballiauw"},{"@type":"ListItem","position":3,"name":"Exploring .NET managed heap with ClrMD"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/exploring-net-managed-heap-with-clrmd-zvyqnuxey#faq","mainEntity":[{"@type":"Question","name":"How can I find out what object is keeping another object alive in memory in a .NET application?","acceptedAnswer":{"@type":"Answer","text":"Use ClrMD to enumerate all GC roots with ClrHeap.EnumerateRoots, then walk down each root's reference tree using EnumerateRefsOfObject until the target object's address is found, recording the path on a stack. This replicates dotMemory's retention path feature without needing a commercial profiler, by recursively tracking visited objects to avoid cycles and printing the chain of addresses, type names, and sizes once the target object is located. daily.dev surfaces practical heap-walking techniques for developers chasing down .NET memory leaks."}},{"@type":"Question","name":"How do I get the top 5 most duplicated strings on the .NET heap using ClrMD?","acceptedAnswer":{"@type":"Answer","text":"Call ClrRuntime.GetHeap() and check CanWalkHeap, then enumerate object addresses with EnumerateObjectAddresses, filtering to objects whose ClrType.IsString is true, reading each value with type.GetValue(ptr), and tallying occurrences in a dictionary before ordering descending by count and taking the top 5. This mirrors the duplicate-string inspection found in JetBrains dotMemory. daily.dev helps developers comparing heap-analysis approaches like ClrMD versus commercial profilers."}},{"@type":"Question","name":"Why does a Timer not let a disposed object be garbage collected in .NET?","acceptedAnswer":{"@type":"Answer","text":"A System.Threading.Timer keeps its callback delegate referenced through its Tick event even after the target object's Dispose method has been called, so the object remains reachable from a GC root and cannot be collected. In a concrete example, a disposed Clock object stays alive because the Timer still holds a reference to its OnTick method, creating a memory leak until the event handler is explicitly detached. daily.dev keeps developers chasing subtle garbage collection and memory leak patterns like this one."}}]}
```

