<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>第 13 章 垃圾回收器 on Go 语言原本</title>
    <link>/zh-cn/part4memory/ch13gc/</link>
    <description>Recent content in 第 13 章 垃圾回收器 on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/zh-cn/part4memory/ch13gc/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>13.1 垃圾回收的基本想法</title>
      <link>/zh-cn/part4memory/ch13gc/basic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/basic/</guid>
      <description>&lt;h1 id=&#34;131-垃圾回收的基本想法&#34;&gt;13.1 垃圾回收的基本想法&lt;/h1&gt;&#xA;&lt;p&gt;垃圾回收（GC）把程序员从手动 &lt;code&gt;free&lt;/code&gt; 中解放出来，代价是运行时要自己判断「哪些内存还有用、&#xA;哪些可以收回」。Go 的 GC 以低延迟为首要目标，它宁可牺牲一点吞吐与内存，也要把卡顿&#xA;（stop-the-world 停顿）压到亚毫秒级。这一节建立 GC 的理论坐标：可达性、标记清扫、三色抽象，&#xA;以及 Go 在 GC 设计空间里的定位。后续各节是这套基本想法的展开。&lt;/p&gt;&#xA;&lt;p&gt;在展开之前，先借用一组贯穿全章的术语。Dijkstra 等人 [Dijkstra et al., 1978] 把一个带 GC 的&#xA;程序拆成两个半独立的角色：赋值器（mutator）与回收器（collector）。赋值器即用户态代码，&#xA;它只做一件与 GC 相关的事，修改对象之间的引用关系，也就是在一张「对象图」上增删有向边；&#xA;回收器则是运行时里负责找出并收回垃圾的那部分代码。本章的全部难处，都源自这两者要同时运行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1311-可达性即存活&#34;&gt;13.1.1 可达性即存活&lt;/h2&gt;&#xA;&lt;p&gt;GC 判断存活的依据是可达性：从一组根（root）出发，凡能沿指针到达的对象都算存活，&#xA;到达不了的就是垃圾。Go 的根集合有三类，全局变量、各 goroutine 栈上的变量、以及寄存器中&#xA;可能存放的指针。回收器从这些根出发遍历对象图，标记一路可达的对象。&lt;/p&gt;&#xA;&lt;p&gt;可达性是一个保守而安全的近似。它并不真正知道某个对象将来还会不会被读到，只知道「从根够不到的，&#xA;程序一定再也用不上」。于是「这块内存是否还有用」这个语义问题，被转化成了一个图遍历问题：&#xA;从根开始走对象图，走得到的留下，走不到的回收。&lt;/p&gt;&#xA;&lt;p&gt;这里还藏着一个 Go 与许多 C/C++ 上的回收器的分野。Go 的 GC 是精确（precise，亦称 type-accurate）&#xA;的：运行时借助编译期生成的类型信息与 span 元数据（&lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt;），&#xA;能准确分辨一个字（word）究竟是指针还是普通整数。保守式回收器做不到这一点，它只能把任何&#xA;「看起来像指针」的位模式都当作指针看待，因而可能误判、且无法移动对象。精确是 Go 后续许多&#xA;设计（写屏障、栈扫描）得以成立的前提。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1312-标记清扫&#34;&gt;13.1.2 标记—清扫&lt;/h2&gt;&#xA;&lt;p&gt;最经典的实现是标记—清扫（mark-sweep），由 McCarthy 在 1960 年实现 Lisp 时首次提出&#xA;[McCarthy, 1960]。它把回收分成两个阶段。标记阶段从根出发遍历对象图，给每个可达对象打上标记：&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.2 写屏障技术</title>
      <link>/zh-cn/part4memory/ch13gc/barrier/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/barrier/</guid>
      <description>&lt;h1 id=&#34;132-写屏障技术&#34;&gt;13.2 写屏障技术&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt; 给出了三色抽象与并发回收的轮廓：回收器把对象从白染灰、由灰染黑，&#xA;让一道「灰色波面」在对象图上单调推进，波面扫过之处即为已确认存活。若赋值器（mutator，&#xA;即用户态代码）在波面推进时停下不动，这套抽象自洽且会正确收敛。问题恰恰在于赋值器不会停。&#xA;并发回收的根本困难是：回收器追踪对象图的同时，赋值器在改写它，两者对同一张图各执一词。&lt;/p&gt;&#xA;&lt;p&gt;这一节要回答的是：赋值器的一次指针写入，凭什么不会让回收器漏标一个存活对象。答案是&lt;strong&gt;写屏障&lt;/strong&gt;&#xA;（write barrier）：编译器在堆指针写入处插入的一小段代码，让赋值器把「我动了哪条边」告知回收器。&#xA;我们先看清并发改图为何会漏标（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1321-%e5%b9%b6%e5%8f%91%e6%94%b9%e5%9b%be%e4%b8%ba%e4%bd%95%e4%bc%9a%e6%bc%8f%e6%a0%87&#34;&gt;13.2.1&lt;/a&gt;），由此引出两条三色不变性&#xA;（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1322-%e4%b8%a4%e6%9d%a1%e4%b8%89%e8%89%b2%e4%b8%8d%e5%8f%98%e6%80%a7&#34;&gt;13.2.2&lt;/a&gt;）；再看堵住漏标的两个屏障族（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1323-%e4%b8%a4%e4%b8%aa%e5%b1%8f%e9%9a%9c%e6%97%8f-dijkstra-%e6%8f%92%e5%85%a5%e4%b8%8e-yuasa-%e5%88%a0%e9%99%a4&#34;&gt;13.2.3&lt;/a&gt;）；&#xA;最后落到 Go 自 1.8 起沿用至今的混合写屏障（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1324-go-%e7%9a%84%e6%b7%b7%e5%90%88%e5%86%99%e5%b1%8f%e9%9a%9c&#34;&gt;13.2.4&lt;/a&gt;），&#xA;理解它为何能一举消除标记终止阶段那次代价高昂的栈重扫。&lt;/p&gt;&#xA;&lt;p&gt;需要先厘清一处常见的名词混淆。本节的「写屏障」是垃圾回收意义上的赋值器屏障，是一段&lt;strong&gt;软件&lt;/strong&gt;&#xA;逻辑，它与 CPU 层面阻止访存重排的内存屏障（memory barrier，见 &lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;）&#xA;是两回事。两者唯一的交点在于：Go 的写屏障实现为保证自身在多核下的正确性，确实依赖了一处访存&#xA;顺序约束，这一点留到 &lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1324-go-%e7%9a%84%e6%b7%b7%e5%90%88%e5%86%99%e5%b1%8f%e9%9a%9c&#34;&gt;13.2.4&lt;/a&gt; 再谈。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1321-并发改图为何会漏标&#34;&gt;13.2.1 并发改图为何会漏标&lt;/h2&gt;&#xA;&lt;p&gt;考虑回收器正扫到一半的某个瞬间。设灰色对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;A&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 指向白色对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，赋值器此时并发地做两件事：&#xA;把一个已被染黑的对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 指向 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，再把 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;A&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的那条引用抹去。于是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 现在只挂在黑色&#xA;对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 之下。回收器认为黑色对象已扫描完毕、不会再访问，而通往 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的灰色入口又被抹掉了，&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 就此从波面上消失，最终被当作垃圾错误回收。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph before[&amp;#34;改图前&amp;#34;]&#xA;        A1[&amp;#34;A 灰&amp;#34;]:::grey --&amp;gt;|ref| B1[&amp;#34;B 白&amp;#34;]:::white&#xA;        C1[&amp;#34;C 黑&amp;#34;]:::black&#xA;    end&#xA;    subgraph after[&amp;#34;改图后：B 漏标&amp;#34;]&#xA;        A2[&amp;#34;A 灰&amp;#34;]:::grey -.-&amp;gt;|删除 ref| B2[&amp;#34;B 白&amp;#34;]:::white&#xA;        C2[&amp;#34;C 黑&amp;#34;]:::black --&amp;gt;|新增 ref| B2&#xA;    end&#xA;    before --&amp;gt; after&#xA;    classDef white fill:#fff,stroke:#333,color:#000&#xA;    classDef grey fill:#bbb,stroke:#333,color:#000&#xA;    classDef black fill:#333,stroke:#000,color:#fff&lt;/pre&gt;&#xA;&lt;p&gt;把这一瞬拆开看，漏标恰由两件事&lt;strong&gt;同时&lt;/strong&gt;发生才成立。[Wilson, 1992] 将其精确为两个条件：&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.3 触发频率及其调步算法</title>
      <link>/zh-cn/part4memory/ch13gc/pacing/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/pacing/</guid>
      <description>&lt;h1 id=&#34;133-触发频率及其调步算法&#34;&gt;13.3 触发频率及其调步算法&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt; 交代了写屏障如何让并发标记与赋值器（mutator）共处，保证「正在回收的同时&#xA;还能继续分配」。这就留下了一个时间问题：什么时候该启动下一轮 GC？太晚，堆已经涨到不可接受的&#xA;体积；太早，回收频繁地白白烧掉 CPU。回答这个问题的，是 Go 从 1.5 起引入、1.18 重新设计的&#xA;&lt;strong&gt;调步器&lt;/strong&gt;（pacer）。本节先把一轮 GC 的完整时序摆出来，再讲清调步器是一个什么样的反馈控制器：&#xA;它要在赋值器持续分配的过程中，赶在堆触到目标之前完成标记。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1331-一轮-gc-的时序&#34;&gt;13.3.1 一轮 GC 的时序&lt;/h2&gt;&#xA;&lt;p&gt;把一轮 GC 拆开看，它在 &lt;code&gt;_GCoff&lt;/code&gt;、&lt;code&gt;_GCmark&lt;/code&gt;、&lt;code&gt;_GCmarktermination&lt;/code&gt; 三个阶段间流转，其间夹着&#xA;两段极短的 STW（stop-the-world）：&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;stateDiagram-v2&#xA;    [*] --&amp;gt; GCoff&#xA;    GCoff --&amp;gt; STW1: heap_live ≥ trigger&#xA;    note right of STW1&#xA;        极短 STW：开启写屏障&#xA;        扫描栈与全局根&#xA;    end note&#xA;    STW1 --&amp;gt; GCmark: 切入并发标记&#xA;    state GCmark {&#xA;        background: 后台标记 worker（约 25% CPU）&#xA;        assist: 赋值器 mark assist（分配快者被征用）&#xA;        background --&amp;gt; assist&#xA;        assist --&amp;gt; background&#xA;    }&#xA;    GCmark --&amp;gt; STW2: 标记队列排空&#xA;    note right of STW2&#xA;        极短 STW：标记终止&#xA;        关闭写屏障并结算&#xA;    end note&#xA;    STW2 --&amp;gt; Sweep: 转入并发清扫&#xA;    Sweep --&amp;gt; GCoff: 清扫随分配惰性推进&#xA;    GCoff --&amp;gt; [*]&lt;/pre&gt;&#xA;&lt;p&gt;整轮的脉络是这样：堆上的存活字节 &lt;code&gt;heap_live&lt;/code&gt; 涨到调步器算出的触发线 &lt;code&gt;trigger&lt;/code&gt;，第一段 STW&#xA;开启写屏障并扫描根（goroutine 栈与全局变量），随即放开世界进入并发标记。标记阶段，后台&#xA;worker 占用约 25% 的 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 持续标记，分配过快的 goroutine 还会被征去做 mark assist&#xA;（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/pacing/#1335-%e8%b5%8b%e5%80%bc%e5%99%a8%e5%8d%8f%e5%8a%a9-mark-assist&#34;&gt;13.3.5&lt;/a&gt;）。标记队列排空后进入第二段 STW 做标记终止，关闭&#xA;写屏障并结算本轮统计量，之后转入并发清扫，清扫工作随后续分配惰性摊销（&lt;a href=&#34;.././sweep&#34;&gt;13.5&lt;/a&gt;）。&#xA;两段 STW 都被压到亚毫秒量级，绝大部分回收工作与赋值器并发进行，这正是 Go 低延迟 GC 的形态。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.4 扫描标记与标记辅助</title>
      <link>/zh-cn/part4memory/ch13gc/mark/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/mark/</guid>
      <description>&lt;h1 id=&#34;134-扫描标记与标记辅助&#34;&gt;13.4 扫描标记与标记辅助&lt;/h1&gt;&#xA;&lt;p&gt;标记是 GC 的主体工作：从根（全局变量、各 goroutine 的栈、寄存器）出发，沿指针把所有可达对象&#xA;置黑（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt; 的三色抽象）。一个朴素的实现会停下整个世界、单线程地走完对象图，这正是&#xA;早年 Go 不堪的停顿来源。go1.5 之后，标记被改造成两件事同时成立的形态：它&lt;strong&gt;与用户程序并发&lt;/strong&gt;地推进，&#xA;又能在用户分配过快时&lt;strong&gt;把代价分摊&lt;/strong&gt;到分配者头上。这一节回答三个问题：标记如何并行展开而不互相踩踏；&#xA;扫描一个对象时怎么知道它的哪些字是指针；以及当用户一边分配、标记一边追赶时，凭什么保证标记不会&#xA;被永远甩在后面。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1341-谁来标记后台-worker-与-25-的预算&#34;&gt;13.4.1 谁来标记：后台 worker 与 25% 的预算&lt;/h2&gt;&#xA;&lt;p&gt;标记的主力是一组后台 goroutine，&lt;code&gt;gcBgMarkWorker&lt;/code&gt;。运行时为每个 P 准备一个，但并不让它们全速&#xA;空转抢占 CPU，而是把整个标记阶段的 CPU 预算固定在 &lt;strong&gt;GOMAXPROCS 的 25%&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime/mgcpacer.go（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;const&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcBackgroundUtilization&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 标记阶段后台 GC 的固定 CPU 占用目标&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;为什么是一个固定比例，而不是「有多少干多少」？这是调步器（&lt;a href=&#34;.././pacing&#34;&gt;13.3&lt;/a&gt;）的核心约定：把 GC&#xA;对吞吐的侵蚀钉在一个可预测的水平上，剩下的 75% 留给用户程序。25% 这个数字本身是工程折中，高了伤&#xA;吞吐，低了标记追不上分配（追不上的缺口再由下文的标记辅助补齐）。&lt;/p&gt;&#xA;&lt;p&gt;固定预算落到具体 worker 上，会按 &lt;code&gt;GOMAXPROCS × 0.25&lt;/code&gt; 算出需要几个「专职」worker，余数用一个「零工」&#xA;worker 凑齐，于是有了三种工作模式：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 三种后台标记 worker 模式（runtime）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerDedicatedMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 专职：整个标记阶段独占一个 P，不被抢占&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerFractionalMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 零工：补齐 25% 预算的小数部分，到点即让出&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerIdleMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 闲时：P 没有用户 goroutine 可跑时顺手标记&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;举例：&lt;code&gt;GOMAXPROCS = 8&lt;/code&gt; 时 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;8&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;8 \times 0.25 = 2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7278em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;×&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，于是有 2 个专职 worker 各占一个 P；若是&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;5&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1.25&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;5 \times 0.25 = 1.25&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7278em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;×&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1.25&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，则 1 个专职 worker 加一个补齐 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;0.25&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的零工 worker。专职 worker 在标记期间不被调度&#xA;器抢占，零工 worker 跑到 &lt;code&gt;fractionalUtilizationGoal&lt;/code&gt; 这个比例就主动让出，闲时 worker 则只在某个 P&#xA;找不到用户活儿干时被 &lt;code&gt;findRunnable&lt;/code&gt;（&lt;a href=&#34;../../../part3concurrency/ch09sched/schedule&#34;&gt;9.4 调度循环&lt;/a&gt;）顺手唤起。三者&#xA;合起来既稳住了 25% 的均值，又不浪费空闲算力。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.5 清扫与位图</title>
      <link>/zh-cn/part4memory/ch13gc/sweep/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/sweep/</guid>
      <description>&lt;h1 id=&#34;135-清扫与位图&#34;&gt;13.5 清扫与位图&lt;/h1&gt;&#xA;&lt;p&gt;标记（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）一旦结束，仍为白色的对象就是不可达的垃圾。把它们占据的内存收回、&#xA;重新交给分配器，是回收的最后一步：&lt;strong&gt;清扫&lt;/strong&gt;（sweep）。如果照搬教科书里「遍历堆、逐个释放死对象」&#xA;的写法，清扫的开销会与死对象的数量成正比，在一个动辄数百万对象的堆上，这是一笔不小的账。Go 的&#xA;清扫绕开了这笔账，它有三处值得讲清楚的设计：清扫靠&lt;strong&gt;位图翻转&lt;/strong&gt;而非逐对象处理完成；它与用户程序&#xA;&lt;strong&gt;并发&lt;/strong&gt;进行，且&lt;strong&gt;惰性&lt;/strong&gt;地把开销摊到分配路径上；以及它&lt;strong&gt;不移动对象&lt;/strong&gt;，因而接受碎片、放弃整理。&#xA;这三点都不是偶然，它们各自对应一处明确的工程取舍，本节逐一拆开。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1351-清扫即位图翻转&#34;&gt;13.5.1 清扫即位图翻转&lt;/h2&gt;&#xA;&lt;p&gt;回收的高效，根子在分配器的数据结构上。第 &lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt; 节讲过，每个 span&#xA;被切成同一尺寸类的等大槽位，并配两份位图：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;allocBits&lt;/code&gt;：哪些槽&lt;strong&gt;已分配&lt;/strong&gt;，分配路径据此找空槽。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;gcmarkBits&lt;/code&gt;：本轮标记中哪些槽被置位，即&lt;strong&gt;存活&lt;/strong&gt;，由标记阶段写入。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;清扫一个 span，核心动作出奇地短，把标记位图&lt;strong&gt;直接当作&lt;/strong&gt;新的分配位图：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// mspan.sweep 的核心（src/runtime/mgcsweep.go，速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mspan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;sweep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preserve&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……此前已处理 finalizer / weak / profile 等 special 记录……&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nalloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;countAlloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// gcmarkBits 中置位的槽数 = 存活数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;allocCount&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nalloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;freeindex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 分配游标归零，下次从头扫空槽&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 关键三行：标记位图翻身成为分配位图&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;allocBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcmarkBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 存活的继续「在用」，未标记的就此「空闲」&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcmarkBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newMarkBits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nelems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 备一份清零的标记位图，留给下一轮 GC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;refillAllocCache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 重建 allocCache 位扫描缓存（见 12.2）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……根据存活数决定 span 的去向，见 13.5.3……&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;翻转之后，被标记的槽在新 &lt;code&gt;allocBits&lt;/code&gt; 里仍是 1，继续算「在用」；没被标记的槽在新位图里是 0，&#xA;就此变成「空闲」，下次分配直接覆写。死对象既不需要被找出来，也不需要被逐个清掉。源码注释把&#xA;这种状态说得很准：一个未标记又未被重新分配的槽，处境「就像挂在自由表上一样」（analogous to&#xA;being on a freelist）。这正是本节标题里「免清扫」（sweep-free）的含义,死对象无需任何显式&#xA;「清理」动作，它们的槽只是在位图翻转的一瞬间被重新解释为可分配。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.6 标记终止阶段</title>
      <link>/zh-cn/part4memory/ch13gc/termination/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/termination/</guid>
      <description>&lt;h1 id=&#34;136-标记终止阶段&#34;&gt;13.6 标记终止阶段&lt;/h1&gt;&#xA;&lt;p&gt;并发标记（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）有一个不平凡的收尾问题：如何&lt;strong&gt;判定标记已经完成&lt;/strong&gt;。在单线程的&#xA;停顿式回收里，这个问题是平凡的，标记线程把灰色队列扫空，标记就结束了。可在并发世界里，&#xA;「我的本地队列空了」从来不等于「全局没有灰色对象了」。当一个工作协程刚把自己的队列扫空，&#xA;另一个 P 上的 mutator 可能正巧执行了一次指针写入，写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）随之染灰了&#xA;一个新对象，把它塞进了那个 P 的本地缓存。判定终止，本质上是要在一个没有全局锁、工作分散&#xA;在各 P 本地缓存里的系统中，确认一个&lt;strong&gt;全局性质&lt;/strong&gt;：灰色集合为空，且未来不会再产生灰色对象。&lt;/p&gt;&#xA;&lt;p&gt;本节回答三件事：终止检测算法如何在无全局协调的前提下断定标记完成（&lt;code&gt;gcMarkDone&lt;/code&gt;）；为此&#xA;仍需的那一次短暂 STW（&lt;code&gt;_GCmarktermination&lt;/code&gt;）做了什么、为何能做到很短；以及为什么一个周期&#xA;里始终保留着首尾两次短 STW，运行时为何选择把每次 STW 的&lt;strong&gt;工作量&lt;/strong&gt;压到与堆栈规模无关，&#xA;而不去追逐字面意义上的零停顿。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1361-并发下标记完成为何难判定&#34;&gt;13.6.1 并发下「标记完成」为何难判定&lt;/h2&gt;&#xA;&lt;p&gt;把标记工作想象成一池灰色对象，分散存放在每个 P 的本地 &lt;code&gt;gcWork&lt;/code&gt; 缓存与若干全局工作缓冲里。&#xA;工作协程不断从池中取出灰色对象、扫描、把新发现的指针染灰投回池中，直到池空。难点在于&#xA;「池空」这个判断本身是分布式的：没有哪个线程能在不停下整个世界的前提下，瞬间窥见所有 P&#xA;的本地缓存都为空。&lt;/p&gt;&#xA;&lt;p&gt;更棘手的是写屏障的存在。只要 mutator 还在运行、写屏障还开着，一次 &lt;code&gt;*p = q&lt;/code&gt; 的指针写入就&#xA;可能凭空产生一个新的灰色对象。于是终止条件不是静态的「此刻池空」，而是动态的「池空，&#xA;且没有任何在途的、尚未被观测到的工作能再次填满它」。运行时用两个计数刻画前半句：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 标记是否已完成的快速判据（速写，runtime/mgc.go）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gcIsMarkDone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// nproc 个标记工作者全部处于等待态，且全局已无可取的标记工作&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nwait&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nproc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gcMarkWorkAvailable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;gcIsMarkDone&lt;/code&gt; 只是必要条件，不是充分条件。它看到的是「此刻」的快照，无法排除某个 P 的&#xA;本地缓存里还压着尚未刷新到全局、因而对其他人不可见的工作。要把必要条件升级为充分条件，&#xA;需要一道&lt;strong&gt;全局屏障&lt;/strong&gt;，强制每个 P 把本地缓存抖落出来、让所有人看见，再复查。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.7 安全点分析</title>
      <link>/zh-cn/part4memory/ch13gc/safe/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/safe/</guid>
      <description>&lt;h1 id=&#34;137-安全点分析&#34;&gt;13.7 安全点分析&lt;/h1&gt;&#xA;&lt;p&gt;标记阶段（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）要扫描一个 goroutine 的栈，找出栈上所有指向堆的指针，把它们当作&#xA;GC 根加入灰色队列。这件事看似直白，实则藏着一个不易察觉的前提：&lt;strong&gt;栈上的一个机器字，到底是不是&#xA;指针？&lt;/strong&gt; 一个 64 位的字，可能是一个堆地址，也可能是一个恰好落在堆地址区间里的整数、一段被半拆&#xA;出来的浮点数、或一个尚未写入的垃圾值。若把非指针误当指针，会平白吊住一块本该回收的内存；若把&#xA;指针漏判成整数，则会回收掉仍在使用的对象,后者是致命的内存破坏。&lt;/p&gt;&#xA;&lt;p&gt;要分辨这两者，运行时需要一份「这一刻，栈上哪些位置存着指针」的精确说明。这份说明并非时时存在：&#xA;寄存器与栈槽的指针 / 非指针归属，随着指令逐条执行不断变化。编译器只在某些&lt;strong&gt;程序点&lt;/strong&gt;上为它生成&#xA;快照，这些点就是&lt;strong&gt;安全点&lt;/strong&gt;（safe-point）。GC 要精确扫描栈，就必须先把目标 goroutine 停在一个安全&#xA;点上。安全点这个概念在调度的抢占一节（&lt;a href=&#34;../../../part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;）已从&#xA;「何时可以打断一个 goroutine」的角度系统讲过；本节从 GC 的角度补足它的另一半含义：&lt;strong&gt;安全点是栈&#xA;上指针信息精确可读的那些瞬间&lt;/strong&gt;。这两半其实是同一件事，本节最后会让它们合流。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1371-栈图编译期记下每个安全点的指针布局&#34;&gt;13.7.1 栈图：编译期记下每个安全点的指针布局&lt;/h2&gt;&#xA;&lt;p&gt;编译器为每个函数生成若干&lt;strong&gt;栈图&lt;/strong&gt;（stack map，又称指针位图 pointer map）。一份栈图是一个位向量&#xA;（&lt;code&gt;bitvector&lt;/code&gt;），第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6595em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 位为 1 表示「栈帧中第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6595em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个字长的槽此刻存着一个指针」。同一个函数在不同&#xA;程序点上的栈布局不同，于是编译器为每个安全点各记一份栈图，并用一张 &lt;code&gt;PCDATA&lt;/code&gt; 表把「程序计数器&#xA;PC」映射到「该 PC 对应哪一份栈图」。运行时扫描某一帧时，拿该帧将要返回到的 PC 去查表，便得到&#xA;这一帧此刻的指针布局。&lt;/p&gt;&#xA;&lt;p&gt;运行时读取栈图的入口是 &lt;code&gt;getStackMap&lt;/code&gt;。它对一帧返回三样东西：局部变量的指针位图、参数的指针位图、&#xA;以及该帧内的&lt;strong&gt;栈对象&lt;/strong&gt;（stack object）记录:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// getStackMap：用帧的返回 PC 查出本帧的指针布局（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkframe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;getStackMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;debug&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bitvector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;objs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stackObjectRecord&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;continpc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 本帧将返回到的 PC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 回退到 CALL 指令处再查表：安全点记录的是「调用点」上的布局&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pcdata&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;pcdatavalue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;abi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PCDATA_StackMapIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 局部变量位图：funcdata 里取出本函数的栈图表，按 pcdata 索引取第几份&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkmap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stackmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;funcdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;abi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FUNCDATA_LocalsPointerMaps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;stackmapdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pcdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 参数位图：另一张表 FUNCDATA_ArgsPointerMaps，同样按 pcdata 索引&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;两个细节值得点出。其一，&lt;code&gt;targetpc--&lt;/code&gt;,查表用的不是返回地址本身，而是退一格落到 &lt;code&gt;CALL&lt;/code&gt; 指令上。&#xA;原因是被打断的帧总是停在「调用了某个下层函数、正等它返回」的位置，编译器恰是在调用点处记录了&#xA;栈布局，所以要对齐到调用点而非调用后的下一条指令。其二，指针信息分两类来源：&lt;code&gt;FUNCDATA_LocalsPointerMaps&lt;/code&gt;&#xA;管局部变量，&lt;code&gt;FUNCDATA_ArgsPointerMaps&lt;/code&gt; 管参数区，两者用同一个 &lt;code&gt;pcdata&lt;/code&gt; 索引各取一份位图。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.8 分代假设与代际回收</title>
      <link>/zh-cn/part4memory/ch13gc/generational/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/generational/</guid>
      <description>&lt;h1 id=&#34;138-分代假设与代际回收&#34;&gt;13.8 分代假设与代际回收&lt;/h1&gt;&#xA;&lt;p&gt;学过 JVM 或 .NET 的人常会问一个尖锐的问题：Go 为什么&lt;strong&gt;不做分代 GC&lt;/strong&gt;？分代是 Java、.NET 等&#xA;主流托管运行时 GC 的标配，被视为高效回收的关键，几乎成了「现代 GC 应当如此」的常识。Go 偏偏&#xA;没采用。这一节讲清分代的思想、它的威力，以及 Go 不走这条路的理由。这里的答案值得讲究：它不是&#xA;「Go 团队没想到」，而是 Go 团队&lt;strong&gt;真的实现过一版非移动的分代 GC，量过性能，最后放弃了&lt;/strong&gt;。这是&#xA;一处极能体现「没有放之四海皆准的设计」的取舍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1381-分代假设与代际回收的机制&#34;&gt;13.8.1 分代假设与代际回收的机制&lt;/h2&gt;&#xA;&lt;p&gt;分代 GC 建立在一条反复被经验证实的观察上，称作&lt;strong&gt;分代假设&lt;/strong&gt;（generational hypothesis）：&#xA;&lt;strong&gt;绝大多数对象都「英年早逝」&lt;/strong&gt;，刚分配不久就不再被引用。它有强弱两个版本，工程上倚重的是弱版本，&#xA;即「年轻对象的死亡率远高于年老对象」。Lieberman 与 Hewitt（1983）、Ungar（1984）正是抓住这条&#xA;规律设计了最早的代际回收器。&lt;/p&gt;&#xA;&lt;p&gt;既然新对象大多速朽，就按「年龄」给对象分代：&lt;strong&gt;新生代&lt;/strong&gt;（young generation）装新分配的对象，&#xA;&lt;strong&gt;老年代&lt;/strong&gt;（old generation）装活过若干轮回收、被认为「站稳脚跟」的对象。回收分两档：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;minor GC&lt;/strong&gt;：只扫新生代。因为垃圾几乎都在这里，且新生代通常很小，扫一遍极快。多数回收都是&#xA;minor GC。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;major GC / full GC&lt;/strong&gt;：偶尔才全堆扫一次，处理那些熬进老年代后才死去的对象。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把回收工作集中在「小而垃圾密集」的新生代上，是分代 GC 威力的来源。用一点记号来量化：设新生代&#xA;存活率为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7167em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;、老年代存活率为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_o&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5806em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，分代假设说 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;≪&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y \ll s_o&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8252em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≪&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5806em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。一次 minor GC 的扫描成本正比于&#xA;新生代的&lt;strong&gt;存活&lt;/strong&gt;对象数（标记-复制类回收器只触碰活对象），约为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y \cdot N_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7306em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.9694em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:-0.109em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;；而它回收掉的垃圾&#xA;约为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;(1 - s_y) \cdot N_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.0361em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.9694em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:-0.109em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7167em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 越小，单位扫描成本换来的回收收益越高，这正是「频繁、廉价的&#xA;minor GC」之所以划算的全部道理。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.9 请求假设与事务制导回收</title>
      <link>/zh-cn/part4memory/ch13gc/roc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/roc/</guid>
      <description>&lt;h1 id=&#34;139-请求假设与事务制导回收&#34;&gt;13.9 请求假设与事务制导回收&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt; 讲过 Go 没有采用分代假设。读者或许会接着问：Go 有没有试过别的&#xA;「对象寿命假设」？有。这一节讲的是 Go 团队认真做过、最终放弃的一个实验，&lt;strong&gt;事务制导回收&lt;/strong&gt;&#xA;（Request-Oriented Collector, ROC）。讲它，不是因为它进了 Go，而是因为一个被放弃的设计，&#xA;往往比成功的设计更能照出约束所在。ROC 像一道做错了的证明题：错处恰好标出了 Go GC 设计空间&#xA;里那条不能逾越的边界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1391-请求假设&#34;&gt;13.9.1 请求假设&lt;/h2&gt;&#xA;&lt;p&gt;服务端程序有一条很强的结构特征：工作以&lt;strong&gt;请求&lt;/strong&gt;为单位。一个请求进来，分配一批对象处理它，&#xA;请求一结束，这批对象绝大多数就该死了。Rick Hudson 与 Austin Clements 在 2016 年把这条观察&#xA;写成了&lt;strong&gt;请求假设&lt;/strong&gt;（request hypothesis）：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;由一个请求创建的对象，倾向于在该请求被处理完成时一同死亡。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;它与分代假设（&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt;）说的不是一回事。分代假设讲的是「年轻对象易死」，&#xA;是一条关于&lt;strong&gt;年龄&lt;/strong&gt;的统计规律；请求假设讲的是「同一请求的对象同生共死」，是一条关于&lt;strong&gt;归属&lt;/strong&gt;&#xA;的结构规律。对服务端来说，后者更贴合现实，也更有力：它不只说某批对象会死，还指明了它们&#xA;&lt;strong&gt;何时&lt;/strong&gt;死、&lt;strong&gt;成批&lt;/strong&gt;死。云上的典型 goroutine 正是这种形状，收一条消息、反序列化、算一遍、&#xA;序列化、把结果丢回 channel 或 socket，然后退出：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 每请求一个 goroutine：分配的对象多数随 goroutine 退出而死&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;net&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这些中间对象&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;compute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 几乎都只在本 goroutine 内活动&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Write&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 函数返回后即成垃圾&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;若能利用这条规律，回收就能从「全局扫描找垃圾」退化成「请求一结束，把它的那批对象整批还&#xA;回去」,既快，又不必停下整个程序做全局 GC。问题只剩一个：怎么知道哪些对象「只属于这个&#xA;请求」？&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.10 终结器</title>
      <link>/zh-cn/part4memory/ch13gc/finalizer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/finalizer/</guid>
      <description>&lt;h1 id=&#34;1310-终结器&#34;&gt;13.10 终结器&lt;/h1&gt;&#xA;&lt;p&gt;垃圾回收的常规剧情到&lt;a href=&#34;.././sweep&#34;&gt;清扫（13.5）&lt;/a&gt;就结束了：标记找出存活对象，清扫把死对象的&#xA;槽位归还给分配器。但有一类对象在死之前还想说一句话。它包着一份非内存资源，一个文件描述符、&#xA;一段 mmap、一个 C 侧分配的句柄，当 Go 这边的包装对象不可达时，那份底层资源也该随之释放，&#xA;而垃圾回收只懂内存，并不知道描述符要 &lt;code&gt;close&lt;/code&gt;。终结器（finalizer）就是为这条缝隙而设的钩子：&#xA;为对象登记一个函数，等垃圾回收判定它不可达时，运行时不立即释放它，而是先调用这个函数。&lt;/p&gt;&#xA;&lt;p&gt;这是一个诱人却危险的机制。诱人在于它看起来像 C++ 的析构函数，能把「资源释放」自动挂到「对象&#xA;消亡」上；危险在于 Go 的对象消亡由垃圾回收决定，时机不可预测，于是一切「必须发生」的释放都&#xA;不能托付给它。本节先把 &lt;code&gt;runtime.SetFinalizer&lt;/code&gt; 的机制讲清楚，再逐条点出它的陷阱，最后介绍&#xA;Go 1.24 引入的 &lt;code&gt;runtime.AddCleanup&lt;/code&gt; 这个更安全的替代品，并给出一条简单的抉择原则。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13101-setfinalizer-的机制&#34;&gt;13.10.1 SetFinalizer 的机制&lt;/h2&gt;&#xA;&lt;p&gt;登记一个终结器只需一行：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;openSomeFd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;SetFinalizer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;SetFinalizer(obj, fn)&lt;/code&gt; 要求 &lt;code&gt;obj&lt;/code&gt; 是一个指针，指向由 &lt;code&gt;new&lt;/code&gt;、复合字面量取址或局部变量取址&#xA;得到的、堆上分配的对象；&lt;code&gt;fn&lt;/code&gt; 是一个只接收一个参数（类型可由 &lt;code&gt;obj&lt;/code&gt; 赋值）的函数。运行时把&#xA;这对关系记在该对象所在 mspan 的一条 &lt;strong&gt;special&lt;/strong&gt; 记录里（&lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&#xA;讲过 mspan 的元数据），而不是塞进对象本身。&lt;code&gt;SetFinalizer(obj, nil)&lt;/code&gt; 撤销登记。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.11 Green Tea：面向局部性的标记</title>
      <link>/zh-cn/part4memory/ch13gc/greentea/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/greentea/</guid>
      <description>&lt;h1 id=&#34;1311-green-tea面向局部性的标记&#34;&gt;13.11 Green Tea：面向局部性的标记&lt;/h1&gt;&#xA;&lt;p&gt;前面几节把标记讲成一件「正确且及时」的事：写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）保证不漏标，标记辅助&#xA;（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）保证标记追得上分配。可这两件事都没碰标记的另一笔账：&lt;strong&gt;标记跑得快不快&lt;/strong&gt;。&#xA;到了多核、大堆的今天，标记的瓶颈往往既不在算法的正确性，也不在 CPU 的算力，而在&lt;strong&gt;内存墙&lt;/strong&gt;：&#xA;处理器大半时间不是在算，而是在等一笔笔缓存未命中的内存读取返回。Green Tea 正是冲着这笔账去的。&lt;/p&gt;&#xA;&lt;p&gt;Green Tea 是 go1.25 引入的新标记算法，go1.26 起&lt;strong&gt;默认开启&lt;/strong&gt;（基线实验特性，用&#xA;&lt;code&gt;GOEXPERIMENT=nogreenteagc&lt;/code&gt; 可以关掉回退到旧标记器），实现集中在 &lt;code&gt;runtime/mgcmark_greenteagc.go&lt;/code&gt;。&#xA;它不改变三色抽象，也不引入任何新的屏障，只重排了标记器&lt;strong&gt;遍历对象的顺序&lt;/strong&gt;，让访存从随机变回顺序。&#xA;这一节把它拆开来讲：它要解决的局部性问题、推迟扫描的核心想法、内联在 span 上的两套位图、span 的&#xA;认领与窃取，以及最终落到稠密 / 稀疏两条扫描路径上的实现。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13111-问题标记是一场缓存灾难&#34;&gt;13.11.1 问题：标记是一场缓存灾难&lt;/h2&gt;&#xA;&lt;p&gt;回到经典的三色标记（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）：从灰色队列取一个对象，扫描它内部的每个指针，把指向的&#xA;白色对象染灰入队，反复直到队空。这是一次以对象为单位的&lt;strong&gt;图遍历&lt;/strong&gt;，而图的边由程序的引用关系决定，&#xA;与对象在堆上的物理位置毫不相干。于是标记器的内存访问近乎随机：扫完手上这个对象，下一个要碰的对象&#xA;可能落在几兆字节之外的另一个 span 上，它的指针位图元数据又在第三处。&lt;/p&gt;&#xA;&lt;p&gt;这种「追着指针满内存乱跑」的访问模式，对现代 CPU 几乎是最坏情况：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;缓存命中率低&lt;/strong&gt;。相邻两次访问落在同一条缓存行的概率很小，绝大多数指针解引用都要等一次主存读取，&#xA;延迟以上百个时钟周期计。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;预取器失效&lt;/strong&gt;。硬件预取依赖可预测的访问步长，而指针追逐没有步长可言，预取器根本猜不中下一个地址。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;元数据访问被摊不开&lt;/strong&gt;。每扫一个对象都要单独取一次它的指针位图、size class 等元数据，这些零散的&#xA;小读取本身又是一串缓存未命中。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;后果是标记吞吐&lt;strong&gt;难以随核数线性扩展&lt;/strong&gt;：加核只是让更多核一起空等内存。这正是 go1.5 把停顿压到亚毫秒&#xA;（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）之后，标记阶段剩下的最大一块成本，也是 Green Tea 要啃的骨头。值得注意的是，&#xA;它瞄准的维度和被放弃的分代、ROC（&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt;、&lt;a href=&#34;.././roc&#34;&gt;13.9&lt;/a&gt;）不同：那两条路想靠&#xA;对象的&lt;strong&gt;时间&lt;/strong&gt;结构（年龄、请求生命周期）少扫一些对象，代价是长期开启的写屏障；Green Tea 想靠对象的&#xA;&lt;strong&gt;空间&lt;/strong&gt;结构（同一 span 上的对象物理相邻）把同样多的对象扫得更快，而不必动屏障。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13112-核心想法推迟扫描按-span-成批&#34;&gt;13.11.2 核心想法：推迟扫描，按 span 成批&lt;/h2&gt;&#xA;&lt;p&gt;Green Tea 的核心想法一句话能说清：&lt;strong&gt;推迟扫描，把指向同一个 span 的对象攒起来一次扫完&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;经典标记是「发现一个、立刻扫一个」。Green Tea 把这一步拆成两段：发现指向某个 span 的指针时，先只&#xA;在那个 span 上&lt;strong&gt;记一笔&lt;/strong&gt;（置标记位），并把这个 span 整体排进一个队列；真正的扫描推迟到稍后，等轮到&#xA;这个 span，再把它上面这段时间里攒下的、所有待扫对象&lt;strong&gt;一次性扫完&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.12 过去、现在与未来</title>
      <link>/zh-cn/part4memory/ch13gc/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/history/</guid>
      <description>&lt;h1 id=&#34;1312-过去现在与未来&#34;&gt;13.12 过去、现在与未来&lt;/h1&gt;&#xA;&lt;p&gt;读完前面十节，读者手里已经握有 Go 垃圾回收器各个零件的现状：三色标记（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）、&#xA;混合写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）、调步器（&lt;a href=&#34;.././pacing&#34;&gt;13.3&lt;/a&gt;）、清扫与回收（&lt;a href=&#34;.././sweep&#34;&gt;13.5&lt;/a&gt;）。&#xA;这一节换一个角度，把这些零件放回时间轴上，看它们是怎样一步步长成今天这副模样的。&lt;/p&gt;&#xA;&lt;p&gt;回收器的演进史，读起来像一条始终朝同一个方向收敛的曲线。从 Go 1.0 到今天，停顿时间下降了约两个&#xA;数量级，而这一路上有一条不变的主线：每一步改动，都服务于「在不打扰用户代码的前提下完成回收」。&#xA;理解了这条主线，下面被采纳与被抛弃的诸多方案，就都能放到同一把尺子上衡量。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13121-被采纳的方案从数百毫秒到亚毫秒&#34;&gt;13.12.1 被采纳的方案：从数百毫秒到亚毫秒&lt;/h2&gt;&#xA;&lt;h3 id=&#34;go-1014朴素的停顿世界标记清扫&#34;&gt;Go 1.0–1.4：朴素的停顿世界标记清扫&lt;/h3&gt;&#xA;&lt;p&gt;最早的回收器是教科书式的标记清扫（mark-sweep），且整个过程停顿世界（stop-the-world，STW）：&#xA;一旦回收开始，所有用户 Goroutine 停下，回收器在单个或少数线程上走完「标记存活、清扫死亡」，&#xA;再放用户代码继续。Go 1.3 把清扫阶段改为与标记并行（parallel，多个回收线程同时干活），&#xA;缩短了总时长，但用户代码仍然是被整段挂起的。&lt;/p&gt;&#xA;&lt;p&gt;这套设计的代价直白地写在停顿时间上：堆越大，需要扫描的对象越多，STW 越长。实测停顿落在数十到&#xA;数百毫秒的量级。对一个正在处理请求的 Web 服务，这意味着尾延迟（tail latency）随时可能被一次回收&#xA;拖出一道百毫秒级的尖峰。这一痛点，定下了此后十年的主题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;go-152015并发标记低延迟的转向&#34;&gt;Go 1.5（2015）：并发标记，低延迟的转向&lt;/h3&gt;&#xA;&lt;p&gt;Go 1.5 是分水岭。Richard Hudson 主导的这次重写，把标记阶段做成与用户代码&lt;strong&gt;并发&lt;/strong&gt;（concurrent）&#xA;执行：回收器一边标记，用户 Goroutine 一边运行、一边分配、一边改写指针。为了在指针被并发改写时&#xA;仍不漏标存活对象，引入了 Dijkstra 风格的写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）。这次重写公开喊出的目标，&#xA;是把 STW 压到 10 毫秒以下，并在博客与 ISMM 2018 的报告里反复强调一句立场：宁可牺牲一点吞吐与&#xA;峰值内存，也要换来可预测的低延迟。这是一次明确的价值排序，而非单纯的性能优化。&lt;/p&gt;&#xA;&lt;h3 id=&#34;go-1617把毫秒继续往下压&#34;&gt;Go 1.6、1.7：把毫秒继续往下压&lt;/h3&gt;&#xA;&lt;p&gt;并发标记落地后，剩下的 STW 集中在回收循环的起止两端。1.6 把回收器状态化、改进了标记终止&#xA;（mark termination）阶段的实现，1.7 让栈收缩（stack shrinking）独立于 STW 进行，停顿从 1.5 的&#xA;个位数毫秒进一步降到一两毫秒以内。这两个版本没有惊人的算法变动，做的是把 1.5 打下的并发框架&#xA;逐处打磨干净。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.13 垃圾回收统一理论</title>
      <link>/zh-cn/part4memory/ch13gc/unifiedgc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/unifiedgc/</guid>
      <description>&lt;h1 id=&#34;1313-垃圾回收统一理论&#34;&gt;13.13 垃圾回收统一理论&lt;/h1&gt;&#xA;&lt;p&gt;本章一路看下来，Go 的并发标记清扫、混合写屏障、调步器，又对比了分代假设、请求制导回收等&#xA;别家的路子。算法五花八门，读者读到此处，心里或许积下一个疑问：这些机制之间，除了「都在做&#xA;垃圾回收」之外，有没有一条共同的脉络，把它们安放进同一个框架？&lt;/p&gt;&#xA;&lt;p&gt;有。Bacon、Cheng、Rajan 三人在 2004 年的论文《A Unified Theory of Garbage Collection》&#xA;里给出了一个出人意料的答案：人们习惯当作两大门派的&lt;strong&gt;追踪式&lt;/strong&gt;（tracing）与&lt;strong&gt;引用计数式&lt;/strong&gt;&#xA;（reference counting），在数学上是同一件事的两种写法，是&lt;strong&gt;对偶&lt;/strong&gt;的；而所有现实中的回收器，&#xA;都是这两端之间的某种混合。用这个理论收束全章，能让人从「记住了若干算法」上升到「看清了算法&#xA;空间的结构」,这正是一章理论收尾应当做的事。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13131-两个门派与它们各自的缺口&#34;&gt;13.13.1 两个门派与它们各自的缺口&lt;/h2&gt;&#xA;&lt;p&gt;先把两端摆清楚。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;追踪式&lt;/strong&gt;从根集合（栈、寄存器、全局变量）出发，沿指针逐步走遍对象图，凡能到达的都标记为&#xA;存活，走完后未被标记的即是垃圾。Go 用的就是这一类（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）。它的好处是天然处理&#xA;循环引用：一个互相指向却无人从外部引用的对象环，从根出发根本到不了，自然被判死。它的代价是&#xA;回收要成批进行：得等一轮遍历走完，才知道谁是垃圾，于是有「停顿」这件事，也才有本章前面那一&#xA;整套为削减停顿而生的并发标记与写屏障机制。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;引用计数式&lt;/strong&gt;给每个对象配一个计数器，记录有多少指针指向它。每次指针赋值，给新指向的对象&#xA;加一、给旧指向的对象减一；计数归零，对象立即回收。它的好处是回收&lt;strong&gt;及时且分摊&lt;/strong&gt;：内存在最后&#xA;一个引用消失的瞬间就还回去，停顿被打散进每一次指针写入里。它的代价正好与追踪式相反：&lt;strong&gt;处理&#xA;不了循环引用&lt;/strong&gt;,环内对象彼此把对方的计数顶在一以上，谁也归不了零，于是泄漏。&lt;/p&gt;&#xA;&lt;p&gt;两个门派各有一个缺口，而且缺口的位置恰好互补。这种「恰好互补」不是巧合，它是下一节那条对偶&#xA;关系在工程表象上的投影。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13132-追踪与计数是对偶&#34;&gt;13.13.2 追踪与计数是对偶&lt;/h2&gt;&#xA;&lt;p&gt;统一理论的核心，是用同一个不动点（fixed point）方程刻画两种算法，二者只是求同一个解的两个&#xA;方向。&lt;/p&gt;&#xA;&lt;p&gt;把内存里的对象看成一张有向图。设对象集合为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;V&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;V&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.22222em;&#34;&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，指针构成的有向边的多重集为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;E&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（多重，&#xA;因为一个对象可以有多个字段指向同一目标）。我们保守地假定：凡从根可达的对象，将来都可能被用&#xA;到，因而不可回收。记根集合为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;R&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;R&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.00773em;&#34;&gt;R&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。对每个对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;V&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;v \in V&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.22222em;&#34;&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，定义它的引用计数 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho(v)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为：来自&#xA;根的引用数，加上来自其他&lt;strong&gt;存活&lt;/strong&gt;对象的引用数。这是一个递归定义，写成不动点方程：&lt;/p&gt;&#xA;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;:&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;R&lt;/mi&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;:&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo&gt;∧&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;&#xA;\rho(v) = \bigl|[\, v : v \in R \,]\bigr| \;+\; \bigl|[\,(w, v) : (w, v) \in E \,\wedge\, \rho(w) &amp;gt; 0 \,]\bigr|&#xA;&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.00773em;&#34;&gt;R&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;∧&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;方括号取多重集，竖线取其基数。读法是：&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;v&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的计数，等于「根直接引用它的次数」，加上「所有&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho(w) &amp;gt; 0&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;w&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 经由边 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;(w, v)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 引用它的次数」。注意右边又出现了 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.625em;vertical-align:-0.1944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，所以这是一个&#xA;递归方程，它的解是一个不动点。关键在于：&lt;strong&gt;这个方程有多个不动点，追踪式与引用计数式分别求出&#xA;其中一个。&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
