<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Chapter 13 Garbage Collector on Go: Under the Hood</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/</link><description>Recent content in Chapter 13 Garbage Collector on Go: Under the Hood</description><generator>Hugo</generator><language>en</language><atom:link href="https://golang.design/under-the-hood/en/part4memory/ch13gc/index.xml" rel="self" type="application/rss+xml"/><item><title>13.1 The Basic Idea of Garbage Collection</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/basic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/basic/</guid><description>&lt;h1 id="131-the-basic-idea-of-garbage-collection"&gt;13.1 The Basic Idea of Garbage Collection&lt;/h1&gt;
&lt;p&gt;Garbage collection (GC) frees the programmer from manual &lt;code&gt;free&lt;/code&gt;, at the cost that the runtime must decide for itself which memory is still useful and which can be reclaimed. Go&amp;rsquo;s GC treats low latency as its first priority: it would rather give up a little throughput and memory than let stalls (stop-the-world pauses) grow beyond the sub-millisecond range. This section sets up the theoretical coordinates of GC: reachability, mark-sweep, the tricolor abstraction, and where Go sits within the GC design space. The sections that follow are the elaboration of this basic idea.&lt;/p&gt;</description></item><item><title>13.2 Write Barrier Techniques</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/barrier/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/barrier/</guid><description>&lt;h1 id="132-write-barrier-techniques"&gt;13.2 Write Barrier Techniques&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././basic"&gt;13.1&lt;/a&gt; sketched the tricolor abstraction and the outline of concurrent collection: the collector recolors objects from white to grey and from grey to black, driving a &amp;ldquo;grey wavefront&amp;rdquo; that advances monotonically across the object graph, and wherever the wavefront has passed is confirmed live. If the mutator (the user-space code) were to halt while the wavefront advances, the abstraction would be self-consistent and would converge correctly. The trouble is precisely that the mutator does not halt. The fundamental difficulty of concurrent collection is this: the collector traces the object graph while the mutator rewrites it, and the two hold conflicting accounts of one and the same graph.&lt;/p&gt;</description></item><item><title>13.3 Trigger Frequency and the Pacing Algorithm</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/pacing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/pacing/</guid><description>&lt;h1 id="133-trigger-frequency-and-the-pacing-algorithm"&gt;13.3 Trigger Frequency and the Pacing Algorithm&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././barrier"&gt;13.2&lt;/a&gt; explained how the write barrier lets concurrent marking coexist with the mutator, guaranteeing that &amp;ldquo;we can keep allocating even while collection is under way.&amp;rdquo; That leaves a question of timing: when should the next round of GC start? Too late, and the heap has already grown to an unacceptable size; too early, and frequent collection burns CPU for nothing. The answer comes from the &lt;strong&gt;pacer&lt;/strong&gt;, introduced in Go 1.5 and redesigned in 1.18. This section first lays out the full timeline of a GC cycle, then makes clear what kind of feedback controller the pacer is: it must finish marking before the heap reaches its goal, all while the mutator keeps allocating.&lt;/p&gt;</description></item><item><title>13.4 Scan Marking and Mark Assist</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/mark/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/mark/</guid><description>&lt;h1 id="134-scan-marking-and-mark-assist"&gt;13.4 Scan Marking and Mark Assist&lt;/h1&gt;
&lt;p&gt;Marking is the main body of GC&amp;rsquo;s work: starting from the roots (global variables, each goroutine&amp;rsquo;s stack, registers),
it follows pointers and blackens every reachable object (the tricolor abstraction of &lt;a href=".././basic"&gt;13.1&lt;/a&gt;). A naive
implementation would stop the whole world and walk the object graph single-threaded, which is exactly where early Go&amp;rsquo;s
unbearable pauses came from. After go1.5, marking was reshaped into a form where two things hold at once: it advances
&lt;strong&gt;concurrently with the user program&lt;/strong&gt;, and when the user allocates too fast it can &lt;strong&gt;amortize the cost&lt;/strong&gt; onto the
allocator itself. This section answers three questions: how marking unfolds in parallel without the workers stepping on
each other; how, when scanning an object, we know which of its fields are pointers; and what guarantees that marking
will never be left permanently behind while the user allocates and marking chases.&lt;/p&gt;</description></item><item><title>13.5 Sweeping and Bitmaps</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/sweep/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/sweep/</guid><description>&lt;h1 id="135-sweeping-and-bitmaps"&gt;13.5 Sweeping and Bitmaps&lt;/h1&gt;
&lt;p&gt;Once marking (&lt;a href=".././mark"&gt;13.4&lt;/a&gt;) finishes, any object still white is unreachable garbage. Reclaiming the
memory it occupies and handing it back to the allocator is the last step of collection: &lt;strong&gt;sweeping&lt;/strong&gt;. If we
followed the textbook recipe of &amp;ldquo;walk the heap and free dead objects one by one,&amp;rdquo; the cost of sweeping would
be proportional to the number of dead objects, and on a heap with millions of objects that is no small bill.
Go&amp;rsquo;s sweep sidesteps that bill. Three of its design points are worth making clear: sweeping is done by
&lt;strong&gt;flipping a bitmap&lt;/strong&gt; rather than processing objects one at a time; it runs &lt;strong&gt;concurrently&lt;/strong&gt; with the user
program and &lt;strong&gt;lazily&lt;/strong&gt; spreads its cost across the allocation path; and it &lt;strong&gt;does not move objects&lt;/strong&gt;, so it
accepts fragmentation and forgoes compaction. None of these three is accidental. Each corresponds to a
definite engineering trade-off, and this section unpacks them one by one.&lt;/p&gt;</description></item><item><title>13.6 Mark Termination Phase</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/termination/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/termination/</guid><description>&lt;h1 id="136-mark-termination-phase"&gt;13.6 Mark Termination Phase&lt;/h1&gt;
&lt;p&gt;Concurrent marking (&lt;a href=".././mark"&gt;13.4&lt;/a&gt;) carries one nontrivial closing problem: how to &lt;strong&gt;decide that marking is complete&lt;/strong&gt;. In a single-threaded, stop-the-world collector this problem is trivial, the marking thread drains the grey queue and marking is over. But in a concurrent world, &amp;ldquo;my local queue is empty&amp;rdquo; never means &amp;ldquo;there are no grey objects anywhere globally.&amp;rdquo; The instant a worker goroutine drains its own queue, a mutator on another P may happen to execute a pointer write, the write barrier (&lt;a href=".././barrier"&gt;13.2&lt;/a&gt;) then greys a new object and stuffs it into that P&amp;rsquo;s local cache. Deciding termination is, in essence, confirming a &lt;strong&gt;global property&lt;/strong&gt; in a system that has no global lock and whose work is scattered across each P&amp;rsquo;s local cache: the grey set is empty, and no further grey objects will be produced.&lt;/p&gt;</description></item><item><title>13.7 Safe-Point Analysis</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/safe/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/safe/</guid><description>&lt;h1 id="137-safe-point-analysis"&gt;13.7 Safe-Point Analysis&lt;/h1&gt;
&lt;p&gt;The marking phase (&lt;a href=".././mark"&gt;13.4&lt;/a&gt;) must scan a goroutine&amp;rsquo;s stack, find every pointer on the stack that points into the heap, and add them to the grey queue as GC roots. This sounds straightforward, but it hides a premise that is easy to overlook: &lt;strong&gt;is a given machine word on the stack actually a pointer?&lt;/strong&gt; A 64-bit word might be a heap address, or it might be an integer that happens to fall within the range of heap addresses, a half-disassembled floating-point value, or a garbage value that has not been written yet. If we mistake a non-pointer for a pointer, we needlessly hold onto a block of memory that should have been reclaimed; if we miss a pointer and treat it as an integer, we reclaim an object that is still in use, and the latter is fatal memory corruption.&lt;/p&gt;</description></item><item><title>13.8 The Generational Hypothesis and Generational Collection</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/generational/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/generational/</guid><description>&lt;h1 id="138-the-generational-hypothesis-and-generational-collection"&gt;13.8 The Generational Hypothesis and Generational Collection&lt;/h1&gt;
&lt;p&gt;People who have studied the JVM or .NET often ask a pointed question: why does Go &lt;strong&gt;not do generational GC&lt;/strong&gt;? Generational collection is standard equipment in the GCs of mainstream managed runtimes such as Java and .NET, treated as the key to efficient collection, almost to the point of becoming the common sense of &amp;ldquo;this is how a modern GC ought to be.&amp;rdquo; Go pointedly does not adopt it. This section explains the idea of generational collection, its power, and Go&amp;rsquo;s reasons for not taking this road. The answer here deserves care: it is not that &amp;ldquo;the Go team did not think of it,&amp;rdquo; but that the Go team &lt;strong&gt;actually implemented a non-moving generational GC, measured its performance, and in the end gave it up&lt;/strong&gt;. This is a case that powerfully illustrates &amp;ldquo;there is no design that fits all circumstances.&amp;rdquo;&lt;/p&gt;</description></item><item><title>13.9 The Request Hypothesis and the Request-Oriented Collector</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/roc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/roc/</guid><description>&lt;h1 id="139-the-request-hypothesis-and-the-request-oriented-collector"&gt;13.9 The Request Hypothesis and the Request-Oriented Collector&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././generational"&gt;13.8&lt;/a&gt; explained why Go did not adopt the generational hypothesis. The reader may go on to ask: did Go ever try a different &amp;ldquo;object lifetime hypothesis&amp;rdquo;? It did. This section is about an experiment the Go team took seriously and ultimately abandoned, the Request-Oriented Collector (ROC). We discuss it not because it made it into Go, but because an abandoned design often reveals where the constraints lie better than a successful one. ROC is like a proof done wrong: the spot where it goes wrong marks exactly the boundary in Go&amp;rsquo;s GC design space that cannot be crossed.&lt;/p&gt;</description></item><item><title>13.10 Finalizers</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/finalizer/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/finalizer/</guid><description>&lt;h1 id="1310-finalizers"&gt;13.10 Finalizers&lt;/h1&gt;
&lt;p&gt;The usual storyline of garbage collection ends at &lt;a href=".././sweep"&gt;sweeping (13.5)&lt;/a&gt;: marking finds the live objects, and sweeping returns the slots of dead objects to the allocator. But there is a class of object that wants to say one last word before it dies. It wraps a non-memory resource, a file descriptor, an mmap region, a handle allocated on the C side, and when the Go-side wrapper object becomes unreachable, the underlying resource ought to be released along with it. Garbage collection only understands memory, though, and has no idea that a descriptor needs to be &lt;code&gt;close&lt;/code&gt;d. The finalizer is the hook designed for exactly this gap: you register a function for an object, and when garbage collection decides the object is unreachable, the runtime does not free it immediately but first calls that function.&lt;/p&gt;</description></item><item><title>13.11 Past, Present, and Future</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/history/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/history/</guid><description>&lt;h1 id="1311-past-present-and-future"&gt;13.11 Past, Present, and Future&lt;/h1&gt;
&lt;p&gt;Having read the previous ten sections, the reader now holds the present state of each part of Go&amp;rsquo;s garbage collector: tri-color marking (&lt;a href=".././basic"&gt;13.1&lt;/a&gt;),
the hybrid write barrier (&lt;a href=".././barrier"&gt;13.2&lt;/a&gt;), the pacer (&lt;a href=".././pacing"&gt;13.3&lt;/a&gt;), and sweeping and reclamation (&lt;a href=".././sweep"&gt;13.5&lt;/a&gt;).
This section takes a different angle: it places these parts back on a timeline and watches how, step by step, they grew into their present shape.&lt;/p&gt;
&lt;p&gt;The evolution of the collector reads like a curve that converges steadily in one direction. From Go 1.0 to today, pause times have dropped by roughly two
orders of magnitude, and along the way one constant theme runs through it all: every change serves the goal of completing collection without disturbing user code.
Once that theme is understood, the many schemes below, both those adopted and those abandoned, can all be measured against the same ruler.&lt;/p&gt;</description></item><item><title>13.12 A Unified Theory of Garbage Collection</title><link>https://golang.design/under-the-hood/en/part4memory/ch13gc/unifiedgc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://golang.design/under-the-hood/en/part4memory/ch13gc/unifiedgc/</guid><description>&lt;h1 id="1312-a-unified-theory-of-garbage-collection"&gt;13.12 A Unified Theory of Garbage Collection&lt;/h1&gt;
&lt;p&gt;Having read through this chapter, we have seen Go&amp;rsquo;s concurrent mark-and-sweep, its hybrid write barrier, and its pacer, and we have set them against other schools of thought such as the generational hypothesis and request-oriented collection. The algorithms come in many flavors, and by this point the reader may have a question building up: beyond the fact that they all &amp;ldquo;do garbage collection,&amp;rdquo; is there a common thread that places them inside a single framework?&lt;/p&gt;</description></item></channel></rss>