Hunting Unity memory leaks — Memory Profiler in practice

Distinguish managed-heap from native-memory problems in Unity, then use snapshot comparisons and references to narrow down suspected leaks.

  • unity
  • performance
  • profiler

Treating every "memory leak" as one kind of problem can send you in the wrong direction in Unity. A managed object may survive because a reference outlives its intended scope, while native objects and resources have a different lifetime. The GC collects unreachable managed objects; it does not directly free native memory.

Three areas to check in Unity

Managed heap: a missing event unsubscribe is one common cause. Static events in particular can keep a managed reference to a subscriber after it leaves the scene. A closure that captures an outer variable and outlives its expected scope, or a List/Dictionary that only ever grows because nothing removes from it, belong to the same family.

// ❌ Subscribed to a static event, never unsubscribed — the event outlives the object
void OnEnable()  => GameEvents.OnScoreChanged += HandleScore;
// Without -= in OnDisable, the static event may keep a reference alive

Native memory: Unity objects such as Texture2D, Mesh, RenderTexture, and AudioClip can have native memory separate from their managed wrappers. For objects you create and own at runtime, check the appropriate cleanup path, such as Destroy(), when they are no longer needed. RenderTexture.Release() releases its rendering resources but does not destroy the object itself. Unload an AssetBundle at an appropriate point, accounting for the lifetime of assets loaded from it.

Editor Play Mode ghosts: a project with domain reload disabled in Enter Play Mode Settings lets static state survive across Editor Play Mode sessions. If memory keeps climbing from the second play onward, or unexpected exceptions appear, check this setting. We covered the same trap from the logging side — see the ghost that a missing unsubscribe creates. Check separately whether the behavior also occurs in a Player build.

Memory Profiler — compare snapshots to investigate growth

Unity's official Memory Profiler shows the current memory composition and objects, while comparing two snapshots helps identify what grew. A single snapshot is useful too, but it is difficult to judge a trend or a leak from one capture alone.

The procedure:

  1. Take a snapshot before the suspected action (scene transition, repeating a specific feature)
  2. Repeat that action several times
  3. Take a snapshot after
  4. Compare the two and look at what grew

Don't judge from a single repetition. The first run can include one-time growth from shader compilation or cache warm-up. If live objects and used memory keep growing under comparable conditions, investigate a possible leak. If growth levels off, check whether a cache or pool explains it. In either case, inspect the objects and their ownership before concluding.

What's holding it — tracing the reference path

Once the diff shows a growing object type, the next question is "who's keeping this alive." Memory Profiler's reference information can help reveal a long-lived owner such as a static collection, an unremoved event subscription, or a manager singleton that survives scene changes.

If the references branch and get confusing, start with those whose lifetime exceeds what you expected. The key is to find why the object remains alive after the point when it should have been released.

Leak or cache — how to tell them apart

Not all growing memory is a bug. Atlas caches, object pools, and resource preloading intentionally hold onto memory. These patterns suggest what to investigate next, not a final verdict.

PatternWhat to check
Growth stops at a certain size after repeated actionsCheck whether a cache or pool is intentional
Live object count keeps growing across scene round tripsInvestigate references and ownership
Total memory stays flat after GC.Collect()Separate reserved managed-heap memory from native usage; this alone does not prove a leak
Memory drops after unloading but exceeds the prior level on the next loadCompare object counts and owners across unload and reload

Repeat scene round trips and record object counts and usage at comparable points to narrow down suspects. A staircase or a plateau is a clue, not proof; check the snapshot's objects and references before concluding.

Summary — a repeatable checklist

  • Round-trip the scene several times first and check whether total heap trends upward
  • Snapshot before/after the suspected action and check which types grew in the diff
  • Trace references from growing objects to owners that outlive their expected scope
  • Distinguish a managed-reference issue (such as a missing -= in OnDisable) from native-resource lifetime
  • If domain reload is disabled, keep in mind that editor reproduction can differ from actual build behavior

When a memory problem shows up as a slowdown or crash, keep the reproduction conditions and timing. Our Rekon records the scene, device, and recent logs at capture time; on supported desktop setups, it can also record video with FFmpeg. It does not replace a memory snapshot, but it can help narrow down where the symptoms appeared.