<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第 12 章 内存分配器 on Go 语言原本</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/</link><description>Recent content in 第 12 章 内存分配器 on Go 语言原本</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/index.xml" rel="self" type="application/rss+xml"/><item><title>12.1 设计原则</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/basic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/basic/</guid><description>&lt;h1 id="121-设计原则"&gt;12.1 设计原则&lt;/h1&gt;
&lt;p&gt;至此我们已看清 Go 程序如何启动、调度器如何把 goroutine 摊到操作系统线程上执行。
那些被调度的代码做的最频繁的一件事，是申请内存：每一次 &lt;code&gt;new&lt;/code&gt;、每一个逃逸到堆上的局部变量、
每一次 slice 扩容，最终都落到同一个入口 &lt;code&gt;runtime.mallocgc&lt;/code&gt;。本章讲这个入口背后的分配器，
本节先不碰它的零件，而是回答一个更靠前的问题：一个为 Go 服务的内存分配器，究竟要满足哪些
互相拉扯的目标，又是依凭哪几个判断把这些目标安顿下来的。读懂了这套取舍，后面几节
（&lt;a href=".././component"&gt;12.2&lt;/a&gt;–&lt;a href=".././tinyalloc"&gt;12.6&lt;/a&gt;）的结构与路径就都有了来由。&lt;/p&gt;
&lt;h2 id="1211-四个互相拉扯的目标"&gt;12.1.1 四个互相拉扯的目标&lt;/h2&gt;
&lt;p&gt;先把范围划清。本章谈的「分配」专指&lt;strong&gt;堆分配&lt;/strong&gt;，即 &lt;code&gt;runtime.mallocgc&lt;/code&gt; 经手的那部分。栈上的
局部变量随函数返回自然回收，不归分配器管；只有逃逸到堆上的对象才进入这套机制。一个对象是
留在栈上还是逃逸到堆，由编译器的逃逸分析在编译期判定：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; f() *&lt;span style="color:#2b91af"&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y := 2 &lt;span style="color:#008000"&gt;// y 的地址被返回，逃逸到堆 → 编译器插入 newobject&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; &amp;amp;y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; g() &lt;span style="color:#2b91af"&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x := 2 &lt;span style="color:#008000"&gt;// x 不逃逸，留在栈上 → 不触发堆分配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;f&lt;/code&gt; 中的 &lt;code&gt;y&lt;/code&gt; 会被编译器改写为一次 &lt;code&gt;runtime.newobject&lt;/code&gt; 调用（&lt;code&gt;new&lt;/code&gt; 关键字同样落到这里），
而 &lt;code&gt;newobject&lt;/code&gt; 只是带上类型信息调一下 &lt;code&gt;mallocgc&lt;/code&gt;。换言之，本章讨论的全部分配路径，入口都是
这一个函数。逃逸分析的细节属于编译器（&lt;a href="../../part5toolchain/ch15compile"&gt;15&lt;/a&gt;），这里只需记住：&lt;strong&gt;进入分配器
的，是那些编译器判定无法安放在栈上的对象&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>12.2 组件</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/component/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/component/</guid><description>&lt;h1 id="122-组件"&gt;12.2 组件&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././basic"&gt;12.1&lt;/a&gt; 说分配器是「快路径无锁、慢路径加锁」的分层结构。这一节给这套结构里的几个
核心组件命名、定位，并落到它们在 go1.26 中的实际形态：每个组件承载什么状态、为何如此设计、
以及它们如何串成一条补货链。读懂了这几样东西，后面的分配路径（&lt;a href=".././largealloc"&gt;12.4&lt;/a&gt;–
&lt;a href=".././tinyalloc"&gt;12.6&lt;/a&gt;）就只是「在这张图上走一遍」。&lt;/p&gt;
&lt;p&gt;为避免落回逐字段翻译源码的窠臼，下文给出的结构体都是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;：只保留与设计相关的
字段，并在注释里说明它为何存在。完整定义可对照 &lt;code&gt;runtime/mheap.go&lt;/code&gt;、&lt;code&gt;mcache.go&lt;/code&gt;、&lt;code&gt;mcentral.go&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="1221-自由表一切的底层手法"&gt;12.2.1 自由表：一切的底层手法&lt;/h2&gt;
&lt;p&gt;在认识各组件之前，先认识它们共用的一个底层手法，&lt;strong&gt;自由表&lt;/strong&gt;（free list）。运行时里大量&amp;quot;固定
大小对象&amp;quot;（mcache、mspan、各类元数据）都由一个叫 &lt;code&gt;fixalloc&lt;/code&gt; 的固定大小分配器管理，它的思想
极简：把一批未分配的内存块用指针串成一条链，&lt;strong&gt;每块内存的头部恰好用作指向下一块的指针&lt;/strong&gt;。
分配就是摘下链头，回收就是把块插回链头，都是 $O(1)$ 的指针操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// fixalloc：固定大小对象的自由表分配器（速写）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; fixalloc &lt;span style="color:#00f"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; size &lt;span style="color:#2b91af"&gt;uintptr&lt;/span&gt; &lt;span style="color:#008000"&gt;// 每个对象的固定大小&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; list *mlink &lt;span style="color:#008000"&gt;// 自由表：空闲块串成的链&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; chunk &lt;span style="color:#2b91af"&gt;uintptr&lt;/span&gt; &lt;span style="color:#008000"&gt;// 当前向操作系统批发来的大块的游标&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nchunk &lt;span style="color:#2b91af"&gt;uint32&lt;/span&gt; &lt;span style="color:#008000"&gt;// 大块中剩余的字节&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; (f *fixalloc) alloc() unsafe.Pointer {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;if&lt;/span&gt; f.list != &lt;span style="color:#00f"&gt;nil&lt;/span&gt; { &lt;span style="color:#008000"&gt;// 自由表里有可复用的块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v := unsafe.Pointer(f.list)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; f.list = f.list.next &lt;span style="color:#008000"&gt;// 摘下链头&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// 自由表空了：从批发来的 chunk 里切一块；chunk 也用尽就向操作系统再批发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;if&lt;/span&gt; f.nchunk &amp;lt; f.size {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; f.chunk = uintptr(persistentalloc(_FixAllocChunk, 0, f.stat))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; f.nchunk = _FixAllocChunk
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; v := unsafe.Pointer(f.chunk)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; f.chunk += f.size
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; f.nchunk -= uint32(f.size)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&amp;ldquo;用空闲块自身的头部存下一块的指针&amp;quot;是自由表的精髓,它不需要额外的元数据数组来记录空闲位置，
空间开销为零。这个手法会在下文反复出现：mspan 内部用它串空闲对象槽，mcache 用它缓存 stack，
理解了它，分配器的许多角落就都通了。&lt;/p&gt;</description></item><item><title>12.3 初始化</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/init/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/init/</guid><description>&lt;h1 id="123-初始化"&gt;12.3 初始化&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././component"&gt;12.2&lt;/a&gt; 把分配器拆成了 mcache、mcentral、mheap、arena 几样零件，但那是一张
静态的图。这些零件并非凭空就位，它们要在程序启动之初被一次性安放好。本节回答的问题是：
当 &lt;code&gt;main&lt;/code&gt; 还没运行、第一次 &lt;code&gt;new&lt;/code&gt; 还没发生时，运行时究竟为分配器铺好了哪些基础设施。&lt;/p&gt;
&lt;p&gt;读懂初始化，关键不在于逐行复述 &lt;code&gt;mallocinit&lt;/code&gt;，而在于看清两件被它确立下来、此后再不更改的
事实：其一，&lt;strong&gt;地址空间是怎样被组织成 arena 的&lt;/strong&gt;，这决定了运行时如何从任意一个堆地址反查出
「它属于哪个 span、里面是不是指针、是否还存活」，是垃圾回收（&lt;a href="../ch13gc"&gt;13&lt;/a&gt;）赖以工作的地基；
其二，&lt;strong&gt;「预留地址」与「提交物理内存」是两件事&lt;/strong&gt;，这解释了为何一个 Go 进程的虚拟内存（VIRT）
常常远大于其常驻内存（RES），而这并不是泄漏。&lt;/p&gt;
&lt;p&gt;分配器是除执行栈外最先完成初始化的子系统之一，由调度器引导阶段调用 &lt;code&gt;mallocinit&lt;/code&gt;
（&lt;a href="../../../part1overview/ch03life/boot"&gt;3.5&lt;/a&gt;）。&lt;/p&gt;
&lt;h2 id="1231-mallocinit启动时铺好地基"&gt;12.3.1 mallocinit：启动时铺好地基&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mallocinit&lt;/code&gt; 做三类事：一批关于常量的自检（编译期算出的 arena 尺寸、位图字数、物理页大小
必须自洽），初始化全局堆 &lt;code&gt;mheap_&lt;/code&gt;，以及为 64 位地址空间播下一组「arena 增长提示」。裁剪后的
速写如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; mallocinit() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 1. 自检：编译期常量必须自洽。例如堆位图字数须为 2 的幂（取模寻址要用），&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 物理页大小须落在 [minPhysPageSize, maxPhysPageSize] 且为 2 的幂。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;if&lt;/span&gt; heapArenaBitmapWords&amp;amp;(heapArenaBitmapWords-1) != 0 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		throw(&lt;span style="color:#a31515"&gt;&amp;#34;heapArenaBitmapWords not a power of 2&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;if&lt;/span&gt; physPageSize == 0 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		throw(&lt;span style="color:#a31515"&gt;&amp;#34;failed to get system page size&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// ... 更多关于 physHugePageSize、size class 边界的检查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 2. 初始化全局堆，并为引导线程分配第一个 mcache。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	mheap_.init()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	mcache0 = allocmcache()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 3. 在 64 位机器上播下 arena 增长提示，从地址空间中部开始。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;if&lt;/span&gt; goarch.PtrSize == 8 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#00f"&gt;for&lt;/span&gt; i := 0x7f; i &amp;gt;= 0; i-- {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#00f"&gt;var&lt;/span&gt; p &lt;span style="color:#2b91af"&gt;uintptr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#00f"&gt;switch&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#00f"&gt;case&lt;/span&gt; GOARCH == &lt;span style="color:#a31515"&gt;&amp;#34;arm64&amp;#34;&lt;/span&gt; &amp;amp;&amp;amp; GOOS == &lt;span style="color:#a31515"&gt;&amp;#34;darwin&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				p = uintptr(i)&amp;lt;&amp;lt;40 | uintptrMask&amp;amp;(0x0013&amp;lt;&amp;lt;28)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#00f"&gt;default&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				p = uintptr(i)&amp;lt;&amp;lt;40 | uintptrMask&amp;amp;(0x00c0&amp;lt;&amp;lt;32)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			hint.addr = p
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;那串自检不是仪式。分配器的许多快路径靠位运算寻址，前提是这些尺寸恰为 2 的幂、彼此整除；
若某次移植或改尺寸破坏了前提，与其让程序在运行时给出难解的越界，不如在启动时当场 &lt;code&gt;throw&lt;/code&gt;。
这是运行时代码的一种笔法：把「本应恒真」的不变量写成启动期断言，把错误挡在最前面。&lt;/p&gt;</description></item><item><title>12.4 大对象分配</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/largealloc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/largealloc/</guid><description>&lt;h1 id="124-大对象分配"&gt;12.4 大对象分配&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././component"&gt;12.2&lt;/a&gt; 的分配层级是为「小而频繁」的对象造的：每 P 一份的 mcache、按尺寸类
共享的 mcentral、全局的 mheap，三层缓存把绝大多数分配收敛成几条无锁位运算。可这套精巧的
机器有一个隐含前提，对象小到能被尺寸类装下。一旦对象超过 &lt;code&gt;maxSmallSize&lt;/code&gt;（go1.26 中为
32768 字节，即 32KB），它就装不进任何尺寸类的槽位，整条补货链对它失去意义。&lt;/p&gt;
&lt;p&gt;大对象（large object）走的是另一条路：跳过 mcache 与 mcentral，&lt;strong&gt;直接向 mheap 按页申请&lt;/strong&gt;
一段连续内存，为它单独造一个 span。这条路在代码上比小对象简短得多，但简短不等于廉价。
本节讲清楚它为何被设计成「绕开缓存」，这条路具体怎么走，以及绕开缓存在工程上要付出什么。&lt;/p&gt;
&lt;h2 id="1241-为什么大对象不进缓存"&gt;12.4.1 为什么大对象不进缓存&lt;/h2&gt;
&lt;p&gt;缓存之所以划算，靠的是两个假设：被缓存的东西&lt;strong&gt;频繁复用&lt;/strong&gt;，且&lt;strong&gt;尺寸整齐&lt;/strong&gt;到可以预先按类
备货。小对象两条都满足，于是 mcache 为每个尺寸类常备一个 span，命中率高、摊销下来近乎免费。
大对象两条都不满足。&lt;/p&gt;
&lt;p&gt;它们低频。一个程序里 32KB 以上的分配，数量级远小于小对象，多是大缓冲区、大切片、
大哈希表的底层数组。为低频对象维护每 P 缓存，备的货大半时间闲置，命中率却上不去，
缓存反而成了纯粹的内存浪费。&lt;/p&gt;
&lt;p&gt;它们尺寸离散。小对象被归进 68 个尺寸类，是因为这个区间内的尺寸足够密集，量化到固定档位的
浪费可控（最坏约 12.5%，见 &lt;a href=".././basic"&gt;12.1&lt;/a&gt;）。大对象的尺寸跨度从 32KB 到数百 MB，
若仍按尺寸类备货，类数会爆炸，每类又难得复用一次。与其按类缓存，不如来一个算一个，
按实际页数现切。&lt;/p&gt;
&lt;p&gt;这正是分层分配器一以贯之的取舍：&lt;strong&gt;把热路径打磨到极致，让冷路径保持简单&lt;/strong&gt;。小对象是热路径，
值得用三层缓存和位图换速度；大对象是冷路径，多写的缓存逻辑省不下多少时间，却凭空添了内存
与复杂度。于是 go1.26 干脆为它另开一个入口 &lt;code&gt;mallocgcLarge&lt;/code&gt;，与小对象的
&lt;code&gt;mallocgcSmall*&lt;/code&gt;、微对象的 &lt;code&gt;mallocgcTiny&lt;/code&gt; 在 &lt;code&gt;mallocgc&lt;/code&gt; 里就按尺寸分流（见
&lt;a href=".././smallalloc"&gt;12.5&lt;/a&gt;），各走各的代码，互不拖累。&lt;/p&gt;
&lt;h2 id="1242-直接向堆要页"&gt;12.4.2 直接向堆要页&lt;/h2&gt;
&lt;p&gt;大对象分配的主干很短。&lt;code&gt;mallocgcLarge&lt;/code&gt; 拿到 mcache 只是为了借用它的统计与采样字段，
真正要 span 的活交给 &lt;code&gt;mcache.allocLarge&lt;/code&gt;。后者做两件事：把字节数向上取整成页数，
再向 mheap 要这么多页：&lt;/p&gt;</description></item><item><title>12.5 小对象分配</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/smallalloc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/smallalloc/</guid><description>&lt;h1 id="125-小对象分配"&gt;12.5 小对象分配&lt;/h1&gt;
&lt;p&gt;小对象指尺寸在 16B 至 32KB 之间的对象（go1.26 中 &lt;code&gt;maxSmallSize = 32768&lt;/code&gt;）。它们是 Go 程序里
最常见的一类分配：一个结构体、一个不算太大的切片底层数组、一条字符串拼接的结果，绝大多数都落在
这个区间。分配器为它们设计的路径，是 &lt;a href=".././component"&gt;12.2&lt;/a&gt; 那套 mcache → mcentral → mheap
层级的主舞台，也是整套内存分配器「快路径无锁」设计（&lt;a href=".././basic"&gt;12.1&lt;/a&gt;）兑现性能承诺的地方。&lt;/p&gt;
&lt;p&gt;这一节把一次小对象分配拆成三步来读：先把请求的尺寸&lt;strong&gt;取整到一个尺寸类&lt;/strong&gt;，再从当前 P 的 &lt;strong&gt;mcache
无锁取槽&lt;/strong&gt;，取不到才落入&lt;strong&gt;加锁补货&lt;/strong&gt;的慢路径。读完会发现，常态下的小对象分配不过是「查一次表、
扫一次位、推一下游标」，慢路径只是为这条快路径兜底。&lt;/p&gt;
&lt;h2 id="1251-第一步把尺寸取整到尺寸类"&gt;12.5.1 第一步：把尺寸取整到尺寸类&lt;/h2&gt;
&lt;p&gt;分配器不会为「恰好 37 字节」单独管理一种内存块。它把 16B 至 32KB 的尺寸空间划成 68 个&lt;strong&gt;尺寸类&lt;/strong&gt;
（size class，&lt;a href=".././basic"&gt;12.1&lt;/a&gt;），每个尺寸类对应一个固定的对象大小（8、16、24、32、48……
直到 32768），分配时把请求向上取整到最近的尺寸类。这一步要做到 $O(1)$，靠的是两张预先生成
（由 &lt;code&gt;mksizeclasses.go&lt;/code&gt; 算出）的查找表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// 把请求尺寸映射到尺寸类，再取出该类的实际对象大小（速写）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; sizeclass &lt;span style="color:#2b91af"&gt;uint8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;if&lt;/span&gt; size &amp;lt;= gc.SmallSizeMax-8 { &lt;span style="color:#008000"&gt;// SmallSizeMax = 1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sizeclass = gc.SizeToSizeClass8[divRoundUp(size, gc.SmallSizeDiv)] &lt;span style="color:#008000"&gt;// 步长 8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#00f"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sizeclass = gc.SizeToSizeClass128[divRoundUp(size-gc.SmallSizeMax, gc.LargeSizeDiv)] &lt;span style="color:#008000"&gt;// 步长 128&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;size = uintptr(gc.SizeClassToSize[sizeclass]) &lt;span style="color:#008000"&gt;// 取整后的真实分配大小&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;spc := makeSpanClass(sizeclass, noscan) &lt;span style="color:#008000"&gt;// 尺寸类 + 是否含指针 = spanClass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为何分成两张表？尺寸类在小区间排得密、大区间排得疏（1024 以下按 8 字节一档，以上按 128 字节
一档），用两个不同步长（&lt;code&gt;SmallSizeDiv = 8&lt;/code&gt;、&lt;code&gt;LargeSizeDiv = 128&lt;/code&gt;）的表，能把映射压成两次
数组下标读取，避免任何循环或除法。&lt;code&gt;divRoundUp&lt;/code&gt; 也只是一次乘加，不走真正的除法指令。&lt;/p&gt;</description></item><item><title>12.6 微对象分配</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/tinyalloc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/tinyalloc/</guid><description>&lt;h1 id="126-微对象分配"&gt;12.6 微对象分配&lt;/h1&gt;
&lt;p&gt;前两节走完了分配器的两端。大对象（&lt;a href=".././largealloc"&gt;12.4&lt;/a&gt;）绕开缓存，直接向 mheap 按页要内存；
小对象（&lt;a href=".././smallalloc"&gt;12.5&lt;/a&gt;）按尺寸类从每 P 的 mcache 里摘一个等大槽位。这一节补上最后、
也是最不起眼的一类对象，&lt;strong&gt;微对象&lt;/strong&gt;（tiny object）：小于 16 byte 且不含指针的那些。它们数量极多，
单个又极小，若也按尺寸类各占一个槽位，浪费会大得惊人。Go 为它们单设了一条路径，把多个微对象
&lt;strong&gt;拼进同一个块&lt;/strong&gt;，用一处简单的「碰撞指针」（bump pointer）手法换来可观的内存节省。&lt;/p&gt;
&lt;h2 id="1261-问题把一个-bool-塞进-16-byte-的槽位"&gt;12.6.1 问题：把一个 &lt;code&gt;bool&lt;/code&gt; 塞进 16 byte 的槽位&lt;/h2&gt;
&lt;p&gt;回顾小对象分配的代价。尺寸类是离散的，分配 $n$ 个字节，实际占用的是「不小于 $n$ 的最近一档
尺寸类」（&lt;a href=".././basic"&gt;12.1&lt;/a&gt;）。最小的几档尺寸类是 8、16、24 byte。于是一个 1 byte 的 &lt;code&gt;bool&lt;/code&gt;
逃逸到堆上，要占满一个 8 byte 槽位，浪费 $7/8$；一个 5 byte 的字符串片段同样占 8 byte。
对单次分配，这点内部碎片无关痛痒；可微对象在真实程序里出奇地常见：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[]byte&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt; 在拼接、切分时产生的短片段；&lt;/li&gt;
&lt;li&gt;逃逸到堆上的标量临时量，如 &lt;code&gt;interface{}&lt;/code&gt; 装箱一个小整数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for range&lt;/code&gt; 取地址、闭包捕获等场合下被迫上堆的小变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些对象每一个都不到 8 byte，却各占一个槽位。把它们累加起来，浪费的就不再是零头。若能把若干
微对象&lt;strong&gt;并排塞进一个槽位&lt;/strong&gt;，让它们共享同一段内存，节省便立竿见影。这正是微对象分配器要做的事。&lt;/p&gt;
&lt;h2 id="1262-微对象分配器一个块一个偏移"&gt;12.6.2 微对象分配器：一个块，一个偏移&lt;/h2&gt;
&lt;p&gt;手法本身朴素。mcache 里为微对象留了两个字段（&lt;a href=".././component"&gt;12.2&lt;/a&gt;）：当前正在填充的 16 byte
块的起址 &lt;code&gt;tiny&lt;/code&gt;，以及块内已用到的偏移 &lt;code&gt;tinyoffset&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>12.7 页分配器</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/pagealloc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/pagealloc/</guid><description>&lt;h1 id="127-页分配器"&gt;12.7 页分配器&lt;/h1&gt;
&lt;p&gt;mheap（&lt;a href=".././component"&gt;12.2&lt;/a&gt;）之下，回答「哪些页空闲、哪些已用」的，是页分配器。它是整个
分配器的地基：mcentral 缺 span 时向 mheap 要新页，大对象（&lt;a href=".././largealloc"&gt;12.4&lt;/a&gt;）直接按页申请，
所有 span 占的页最终都从这里切出来。页分配器还要把长期闲置的页交还操作系统。这两件事，一头连着
分配的速度，一头连着进程的常驻内存，恰是 Go 内存系统里那条最持久的张力。&lt;/p&gt;
&lt;p&gt;本节先把页分配器要解的问题摆清楚，再看早期 treap 的设计与软肋，然后展开 Go 1.14 那次把它换成
位图加基数树的重写，这是「换数据结构换性能」最干净的一个例子。最后看两条与之共生的机制：每 P
的页缓存，以及把内存还给操作系统的 scavenging，连同 &lt;code&gt;GOMEMLIMIT&lt;/code&gt; 这个软上限。&lt;/p&gt;
&lt;h2 id="1271-问题快速找一段连续空闲页"&gt;12.7.1 问题：快速找一段连续空闲页&lt;/h2&gt;
&lt;p&gt;页分配器要回答的核心问题只有一句：在巨大的堆地址空间里，快速找到一段足够长的连续空闲页。
「连续」是硬约束，一个 span 占的是物理上相邻的若干页（&lt;a href=".././component"&gt;12.2&lt;/a&gt;），不能东拼西凑。
围绕这一句，还有几条同样要满足的要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分配与归还都要快，且归还的页要能被后续分配重新利用；&lt;/li&gt;
&lt;li&gt;多个 P 会同时申请页，数据结构不能被一把全局锁锁死；&lt;/li&gt;
&lt;li&gt;元数据本身的内存开销要可控，堆可以很大（64 位上地址空间按 TiB 计）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把堆想象成一条极长的纸带，每一页是带上的一格。分配是「找一段够长的连续空格、涂黑」，归还是
「把某段涂回空白」。难点全在「够长的连续」与「快」这两个词的合取上：单看某一格是否空闲很容易，
难的是不逐格扫描就知道「从这里往后有没有连着 $N$ 格的空白」。&lt;/p&gt;
&lt;h2 id="1272-早期的-treap-与它的软肋"&gt;12.7.2 早期的 treap 与它的软肋&lt;/h2&gt;
&lt;p&gt;Go 1.14 之前，页分配器用一棵 treap（树堆）组织空闲的页区间，按区间的起始地址作二叉搜索树的键、
按一个随机优先级维持堆序，从而在期望意义上保持平衡。每个节点是一段连续空闲页。查找一段长度为
$N$ 的空闲区间、分配后的区间分裂、归还后的相邻区间合并，都借树的结构完成，单次操作的期望复杂度
是 $O(\log n)$，$n$ 为空闲区间的个数。&lt;/p&gt;
&lt;p&gt;这套设计能用，但在大堆、高并发下暴露出三处软肋。其一，$O(\log n)$ 看着不坏，可树越大常数越沉，
每次查找都要从根往下走若干层。其二，treap 是指针链接的动态结构，每跳一层就是一次可能未命中缓存的
指针追逐（pointer chasing），在现代 CPU 上，一次 LLC 未命中的代价抵得上上百条算术指令，树的随机
内存布局让缓存几乎帮不上忙。其三，也是最致命的，整棵树由一把全局锁保护，任何一个 P 申请或归还
页都要先抢到这把锁，核数一多，锁争用直接成为吞吐瓶颈。换言之，treap 的问题不在算法复杂度的
渐进阶，而在它的内存访问模式与并发模型，与多核硬件不合。&lt;/p&gt;</description></item><item><title>12.8 内存统计</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/mstats/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/mstats/</guid><description>&lt;h1 id="128-内存统计"&gt;12.8 内存统计&lt;/h1&gt;
&lt;p&gt;分配器一边干活，一边记账。每从操作系统批发一段地址空间、每切出一个 span、每分配或清扫
一个对象，运行时都会把对应的计数累加到一组全局变量里（&lt;code&gt;runtime.memstats&lt;/code&gt;）。这套账目不是
事后补记的统计报表，而是分配与回收过程中顺手写下的流水。它有两个去处：一是对外，让用户和
监控系统看见进程的内存形态；二是对内，&lt;a href="../../ch13gc/pacing"&gt;GC 的调步器（13.3）&lt;/a&gt;与
&lt;a href=".././pagealloc"&gt;软内存限制 &lt;code&gt;GOMEMLIMIT&lt;/code&gt;（12.7）&lt;/a&gt;都要读这套账来决定「下一次该在多大的堆上
触发回收」。换言之，记账闭合了一条反馈回路：分配产生数据，数据驱动决策，决策又约束分配。&lt;/p&gt;
&lt;p&gt;这一节先讲清楚账目里那几个最常被误读的字段，再讲读取它们的两套接口，最后说明这套数据如何
驱动 GC 的节奏，以及在 pprof 与监控里该怎么看。&lt;/p&gt;
&lt;h2 id="1281-账目里有什么关键字段速写"&gt;12.8.1 账目里有什么：关键字段速写&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;runtime.MemStats&lt;/code&gt; 是对外暴露的快照结构，字段近三十个。逐个翻译没有意义，真正要分清的是
两条平行的序列。下面是裁剪后的速写，只留与「读懂内存形态」相关的字段，并在注释里说明它
量的是什么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// runtime.MemStats（速写）：两条平行序列 + GC 节奏&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; MemStats &lt;span style="color:#00f"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// [真实在用的内存：Alloc / Inuse 系列]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapAlloc &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 存活堆对象的字节数：已分配但 GC 尚未清扫的也算在内&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapInuse &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 至少含一个对象的 span 占的字节（含尺寸类带来的内部碎片）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapObjects &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 存活堆对象个数（= Mallocs - Frees）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// [向操作系统要来的地址空间：Sys 系列]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Sys &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 从 OS 取得的虚拟地址空间总量，是下面各 XSys 之和&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapSys &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 为堆保留的地址空间（含已保留未提交的部分，见 12.3）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapIdle &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 空闲 span 占的字节：无对象，可还给 OS 或留作复用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HeapReleased &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 已用 madvise 还给 OS 的物理内存（仍占地址空间）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// [GC 节奏]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NextGC &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 下一轮 GC 的目标堆大小，目标是让 HeapAlloc ≤ NextGC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NumGC &lt;span style="color:#2b91af"&gt;uint32&lt;/span&gt; &lt;span style="color:#008000"&gt;// 已完成的 GC 轮数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PauseTotalNs &lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 程序启动至今 STW 暂停的累计纳秒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PauseNs [256]&lt;span style="color:#2b91af"&gt;uint64&lt;/span&gt; &lt;span style="color:#008000"&gt;// 最近若干轮的 STW 暂停时长（环形缓冲）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;把字段分成「真实在用」与「地址空间」两摞，是读懂内存统计的全部要害。&lt;/p&gt;</description></item><item><title>12.9 过去、现在与未来</title><link>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/history/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part4memory/ch12alloc/history/</guid><description>&lt;h1 id="129-过去现在与未来"&gt;12.9 过去、现在与未来&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容对标 Go 1.26。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;分配器不是一蹴而就的，它随 Go 的演化不断被打磨。回顾这条演进线，能看清当前设计是从哪些权衡里
长出来的，也能瞥见未来的方向。一个值得先记住的判断是：分配器的每一次大改动，几乎都不是为了
「分配本身更快」，而是为了在分配速度、内存占用、与垃圾回收的协同这三者之间重新落子。读懂这条
主线，下面几次重写就不再是孤立的版本琐事，而是同一道工程命题被反复求解的过程。&lt;/p&gt;
&lt;h2 id="1291-过去从-tcmalloc-到-go-化"&gt;12.9.1 过去：从 tcmalloc 到 Go 化&lt;/h2&gt;
&lt;p&gt;Go 分配器最初是 &lt;strong&gt;tcmalloc&lt;/strong&gt;（&lt;a href=".././basic"&gt;12.1&lt;/a&gt;）的一个 Go 移植：继承了线程缓存、尺寸类、
中心仓库这套骨架（&lt;a href=".././component"&gt;12.2&lt;/a&gt; 的 mcache / mcentral / mheap 正是它的三层对应）。
这套骨架解决的是同一个老问题：多线程下 &lt;code&gt;malloc&lt;/code&gt; 的锁争用。tcmalloc 的答案是把绝大多数分配挡在
每线程的本地缓存里，无锁完成，Go 原样继承了这个判断，只是把「每线程」换成了更贴合其调度模型的
「每 P」（&lt;a href="../../../part3concurrency/ch09sched/mpg"&gt;9.3&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;但 Go 分配器逐步「Go 化」，长出了 tcmalloc 没有、也不需要的东西。最根本的一处，是为
&lt;strong&gt;精确垃圾回收&lt;/strong&gt;服务的元数据。C 的 tcmalloc 服务于手动 &lt;code&gt;free&lt;/code&gt;：调用者明确告诉它「这块还给你」，
它无须知道对象内部哪里是指针、哪里是标量，一块内存在它眼里只是一段待复用的字节。Go 没有手动
&lt;code&gt;free&lt;/code&gt;，回收由 GC 接管，而精确 GC 要能从任意一个堆地址出发，判断「这里是不是一个指针、它指向
的对象是否存活」。这就要求分配器在交出每一块内存的同时，附带足够的类型与存活信息：每个 span 的
指针位图（哪些字是指针）、尺寸类里是否含指针的 &lt;code&gt;noscan&lt;/code&gt; 标记、以及与清扫共用的标记位
（&lt;a href=".././component"&gt;12.2&lt;/a&gt; 里 mspan 的 &lt;code&gt;gcmarkBits&lt;/code&gt;、&lt;a href="../../ch13gc/sweep"&gt;13.5&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;这层元数据是 Go 分配器与其祖先最深的分野。它不是锦上添花，而是「与 GC 共生」这条设计原则
（&lt;a href=".././basic"&gt;12.1&lt;/a&gt;）在数据结构上的兑现：分配器交出内存的那一刻，就已经为日后的扫描与回收
铺好了路。代价也实打实，每一块堆内存都要为这层信息付出额外的空间，而这层信息存在哪里、怎样
组织，恰恰成了后续多次重写的战场。&lt;/p&gt;</description></item></channel></rss>