Module

Spotting a memory leak

Sawtooth vs staircase patterns, deploy markers, and why RSS climbing while traffic stays flat is the tell.

The pattern nobody draws on a whiteboard

You get paged. Memory is high. Is it a leak, or did traffic spike?

The answer is usually in the shape of the graph, not the number at the right edge.

Sawtooth: healthy GC behavior

A healthy JVM (or Node process with a decent GC) shows a sawtooth pattern:

  • Memory climbs as objects are allocated
  • GC runs, memory drops sharply
  • The floor after each drop stays roughly the same
    /\    /\    /\
   /  \  /  \  /  \
  /    \/    \/    \

This is normal. GC is working. Don’t page someone for this.

Staircase: the leak signature

A memory leak shows a staircase:

  • Memory still drops during GC (so it’s not “GC is broken”)
  • But each cycle’s floor is higher than the last
  • Over hours, RSS trends upward in steps
         /\
        /  \    /\
       /    \  /  \    /\
      /      \/    \  /  \
     /              \/

The process is holding references it shouldn’t. Objects survive collection. The heap grows until OOM or someone restarts the pod.

Deploy markers: correlation ≠ causation

Dashboards often overlay deploy markers on metric graphs. Useful — but dangerous if you treat correlation as causation.

A deploy marker at 02:14 doesn’t mean the deploy caused the problem. Check:

  • Did the pattern start at the deploy, or was it already climbing?
  • Did memory jump at deploy (new code path) or continue the same staircase?

If RSS was already staircasing before the deploy, the leak predates the release. Rolling back won’t fix it.

RSS vs traffic: the decoupling test

The fastest way to rule out “the site is just busy”:

Plot RSS and request volume on the same time window.

Pattern RSS Traffic Verdict
Traffic growth Rises with traffic Rising Probably fine
Memory leak Staircase up Flat Leak
Bad deploy May spike at deploy Any Investigate deploy
Cache warmup Rises then plateaus May rise Often normal post-deploy

If RSS climbs while RPS is flat, that’s not “the site is popular” — that’s a leak.

What to do when you confirm a leak

  1. Don’t restart yet (unless you’re about to OOM-kill production). You lose the heap dump.
  2. Capture a heap dump or enable profiling if you can do it safely.
  3. Check recent deploys, config changes, and new dependencies.
  4. Look for unbounded caches, growing collections, or event listeners that never unsubscribe.
  5. If you can’t diagnose live: rolling restart buys time, but the leak comes back.

Try it yourself

Use the interactive panel below. Toggle between healthy and leak states. Turn deploy markers on and off. Hide request volume to see the decoupling.

Then ask yourself: would you have caught this at 2am, half asleep, on a phone?

Interactive
RSS (MB) — sawtooth
350400450500550time →deploy
Request volume (req/min)1182

Traffic and memory move independently in healthy systems too.

Is this a leak or traffic growth?