serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Contents/Chapter 44 / 16

Why memory fills up — and GC

Who clears away data nobody needs any more, and when? The cleaner walking the storeroom only takes boxes nobody claims — a box whose ticket someone still holds is never touched.

Every time a program makes new data it takes free space from the heap: allocation. If nothing is ever reclaimed the heap fills and the program dies. This chapter shows how a computer decides that data is "done with", and what a garbage collector actually does. Follow a short loop that loads photos and makes thumbnails.

① How does it know? Reachability

The computer cannot read your intent, so it uses a mechanical rule: start from live variables, follow references; anything you can reach is alive, anything you cannot is garbage. Overwrite img with a new photo's address and the previous box becomes unreachable — not erased, just unreachable. That is the box that dims and gets struck through.

② What GC does — mark, then sweep

The classic collector marks everything reachable from the stack roots, then sweeps up the unmarked boxes and returns their space. So GC never removes what "looks useless" — only what is provably unreachable.

③ Not free — the GC pause

If the program kept mutating references mid-mark the bookkeeping would break, so collectors briefly stop your code. That stop is the GC pause, and it grows with heap size and live-set size. It is one reason server latency occasionally spikes.

④ The real problem is holding, not leaking

Memory still fills in GC languages, for two reasons: allocating faster than collecting, or someone holding references so GC is not allowed to reclaim. Caches that never evict, lists that keep finished requests, long-lived objects pinning short-lived data — all of that is retention.

Step 1/8Speed
program
1main() {
2 album = []PC ◀
3 for i in 0..2 {
4 img = load("photo" + i)
5 thumb = resize(img)
6 album.push(thumb)
7 }
8 show(album)
9}

An empty array is created on the heap (@1); album holds its tag. This chapter asks one question — why does memory fill up?

CPU0
staterunningPC · position→ line 2
Stack↑ newest frame on top
main()
album = @1
Heap
@1Array []

Try it in the simulator

  1. 1.In the same 'GC pressure demo' preset, leave heap at 64MB and run 10 seconds — watch young GC fire 3 times while the KPI panel's p99 spikes to 91.9ms.
  2. 2.Raise JVM heap (MB) to 2,048 in the inspector and the same 10 seconds sees zero GC runs — Spring's CPU stays around 32% (peak 53%) either way; only latency changes.
  3. 3.See why 'CPU has headroom so it's fine' is wrong, from one knob: heap size.
Try it in the simulator →

Terms in this chapter

allocation
Taking free space from the heap for new data.
reachable
Findable by following references from live variables. GC's survival test.
garbage
Data no variable can reach any more.
mark & sweep
The two basic GC phases: mark what is reachable, sweep the rest.
GC pause
Time your program is stopped while the collector works.
retention
Unused data kept alive because something still holds a reference. The classic server memory incident.

Why it evolved this way

Programmers used to free memory by hand, and it was the most-bugged code in history — double frees, use-after-free, forgotten frees. So most modern languages adopted reachability-based collection. The price is pauses and some CPU overhead.

GC only takes what nothing can reach — hold a reference and it can never take it.

The remaining problem

So what makes code hold references for a long time? The next chapter's waiting is a prime culprit →

← Previous

Chapter 3. Stack and heap — what goes where

Next →

Chapter 5. Processes — the first way to share a CPU