Compare

How memory gets freed

Every program has to give memory back. The question is who does it, when, and on which thread. Four languages answer it four different ways, and each answer shows up on the thread lanes.

Rust: the owner frees it, right away

No collector at all. A value is freed when its owner goes out of scope, before the next line runs, on the thread that owns it. A value moved into a thread is freed by that thread.

RustRust frees memory at the end of a scope, not later

No garbage collector. Each value is freed by its owner, at a line you can point to, on the owner's thread.

012345678910main: create a, b, cnewmain: use tmptmpmain: free tmpfreemain: after the blockaftermain: spawn worker, move bspawnjoin()main: main endsendmain: free ccmain: free aaworker: print bbworker: free bfree
Play it step by step

Python: reference counts, plus a collector for cycles

CPython frees an object the moment its last reference goes, inside the del. Objects that point at each other never reach a count of 0, so they wait in memory until the cycle collector runs.

PythonWhen Python frees an object, and when it waits for the collector

del frees an object the moment its last reference goes. Two objects that point at each other stay in memory until the cycle collector runs.

0123456789main: a = Buffer("a")a =main: del a: free adel aprintmain: make b, c, link themb, cmain: del b, c: nothing freeddelprintmain: gc.collect(): free b, cgcprintobject a: alive: 1 referencealiveobjects b and c: alive: main + each otheraliveobjects b and c: unreachable, still in memorystuck
Play it step by step

Go: a collector that runs alongside you

Go stops the world briefly to start a collection, marks while your goroutines keep running, and stops once more to finish. Most of a collection is the concurrent mark. The goroutine that allocates fast starts the collection and is made to help mark.

GoGo's garbage collector runs while your goroutines run

Two brief stop-the-world steps bracket a concurrent mark. The goroutine that allocates starts the collection and helps mark; the others barely notice.

012345678910allocatemain: stop the world: start GCstopmain: allocateallocmain: mark assistallocateallocateticktick: runs during the marktickconcurrent markGC worker: stop the world: finish GCstop
Play it step by step

Java: a pause that stops every thread

When an allocation doesn’t fit, that thread asks for a young collection. G1 runs it at a safepoint, so every Java thread stops, including ones that never allocate, while GC worker threads copy the live objects in parallel.

JavaA young GC pause stops every Java thread

When main's allocation doesn't fit, it asks for a young collection. Every Java thread stops at a safepoint while GC threads copy the live objects.

012345678910allocatemain: allocation fails: ask for a GCaskallocateticktickVM Thread: young collection at a safepointGCGC Thread#0: copy live objectscopyGC Thread#1: copy live objectscopy
Play it step by step

Side by side

Who frees memory When Do your threads stop?
Rust the owner, at the end of its scope at a line you can see no
Python reference counting; the cycle collector for cycles when the last reference goes; cycles later not for reference counting
Go GC worker goroutines, with help from the allocating one when a collection starts twice, briefly, per collection
Java (G1 young) GC worker threads when an allocation doesn’t fit yes, for the whole pause

Common questions

Which languages stop the world for garbage collection?

In these timelines, Java's G1 young collection stops every Java thread at a safepoint, and Go stops the world twice per collection around a concurrent mark. Rust has no collector, and Python frees most objects by reference counting, without one.

Does Rust have a garbage collector?

No. Each value is freed when its owner goes out of scope, before the next line runs, on the owner's thread.

Why does Python have a garbage collector if it uses reference counting?

Reference counting can't free objects that refer to each other. After del, such a cycle stays in memory until the cycle collector runs.

Every timeline here is checked against a real program in CI. How we verify