<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>三色标记 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0/</link><description>Recent content in 三色标记 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Wed, 27 May 2026 11:15:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0/index.xml" rel="self" type="application/rss+xml"/><item><title>Go GC 为什么不能只看 STW：三色标记和写屏障才是你该先算的账</title><link>https://blog.cpdd.fyi/posts/go-gc-three-color-concurrent/</link><pubDate>Wed, 27 May 2026 11:15:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-three-color-concurrent/</guid><description>&lt;p&gt;服务内存从 800MB 慢慢涨到 2.6GB。&lt;/p&gt;
&lt;p&gt;曲线不陡，不像雪崩。它只是每天往上爬一点，重启以后掉下来，过几天又回到老地方。告警一响，群里很快会出现两个动作：先怀疑泄漏，再问 &lt;code&gt;GOGC&lt;/code&gt; 要不要调。&lt;/p&gt;
&lt;p&gt;这两个反应都不离谱，但都太早。&lt;/p&gt;
&lt;p&gt;在 Go 服务里，内存涨可能是真泄漏，也可能只是 live heap 变大了；可能是分配速率太高，也可能是 GC 目标允许堆多长了一段；还有一种常见误会：堆已经回收了，但 RSS 没有按你期待的节奏马上降下去。&lt;/p&gt;
&lt;p&gt;真正要命的不是内存涨，而是你把 GC 当黑箱。&lt;/p&gt;
&lt;p&gt;一旦把它当黑箱，排查就会变成猜参数：&lt;code&gt;GOGC&lt;/code&gt; 调大一点？调小一点？要不要加 &lt;code&gt;GOMEMLIMIT&lt;/code&gt;？要不要手动 &lt;code&gt;runtime.GC()&lt;/code&gt;？&lt;/p&gt;
&lt;p&gt;这些动作不是不能做。问题是，在你没看懂 GC 正在算什么账之前，调参很容易变成给线上服务摇骰子。&lt;/p&gt;
&lt;h2 id="gc-不是保洁员是成本调度器"&gt;GC 不是保洁员，是成本调度器&lt;/h2&gt;
&lt;p&gt;很多人对 GC 的第一印象是“内存不用了，运行时帮我扫掉”。这个说法只说中了一半。&lt;/p&gt;
&lt;p&gt;另一半更重要：GC 每工作一次，都要付成本。&lt;/p&gt;
&lt;p&gt;扫得太勤，CPU 被回收器吃掉，吞吐会掉，延迟也可能抖。扫得太晚，内存峰值会上去，容器可能还没等你优雅回收，就先把进程杀掉。&lt;/p&gt;
&lt;p&gt;所以 Go GC 不像保洁员，看到垃圾就立刻冲上去扫。它更像一个拿着预算表的人：上一轮还有多少对象活着？这轮又分配了多少？再拖一会儿，内存扛不扛得住？现在开始扫，CPU 又吃不吃得消？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GC 调的不是洁癖，是成本。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GOGC&lt;/code&gt; 就是这笔账里最常见的旋钮。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GOGC=100&lt;/code&gt; 不是“内存到 100MB 就触发 GC”。它更接近一个增长比例：在上一轮存活堆的基础上，允许新分配的堆内存再长一段，再启动下一轮 GC。Go 官方 GC Guide 给出的目标堆公式可以简化理解为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目标堆 ≈ live heap + (live heap + GC roots) × GOGC / 100
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有个很容易误会的地方：这个目标不是你容器的内存上限，也不是进程 RSS 上限。它只是运行时决定“什么时候该开始下一轮 GC”的一个目标。&lt;/p&gt;</description></item></channel></rss>