<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>blacklee (黑哥)</title>
    <link>https://ruby-china.org/blacklee</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Understanding Ruby GC through GC.stat</title>
      <description>&lt;ul&gt;
&lt;li&gt;Origin Post: &lt;a href="https://www.speedshop.co/2017/03/09/a-guide-to-gc-stat.html" rel="nofollow" target="_blank"&gt;https://www.speedshop.co/2017/03/09/a-guide-to-gc-stat.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文 [创作时间 2017-03-09]: &lt;a href="https://www.speedshop.co/2017/03/09/a-guide-to-gc-stat.html" rel="nofollow" target="_blank"&gt;https://www.speedshop.co/2017/03/09/a-guide-to-gc-stat.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译文：&lt;a href="https://github.com/blacklee/RubyGC-CN/blob/master/a-guide-to-gc-stat.md" rel="nofollow" target="_blank"&gt;https://github.com/blacklee/RubyGC-CN/blob/master/a-guide-to-gc-stat.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;译注：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;带有 (???) 表明我对此句的翻译拿捏不准。水平有限。。&lt;/li&gt;
&lt;li&gt;「free」这个词拿捏不准就大多保留了原文。我的理解是这样的：「free object」应该是释放了对象（这个是进程内部的操作），如果译掉的话，又可能被理解成释放内存（这是进程和操作系统之家的操作）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;作者：&lt;a href="http://twitter.com/nateberkopec" rel="nofollow" target="_blank" title=""&gt;&lt;strong&gt;Nate Berkopec&lt;/strong&gt;&lt;/a&gt;，来自&lt;a href="https://www.speedshop.co/" rel="nofollow" target="_blank" title=""&gt;&lt;em&gt;speedshop&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;概要：你有没有想过 Ruby 的 GC 是如何工作的？让我们看看我们能从 Ruby 为我们提供的&lt;code&gt;GC.stat&lt;/code&gt;哈希里学到什么。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;大多数的 Ruby 程序员都不清楚垃圾回收在运行时是如何工作的：什么触发了它，它运行得多频繁，它收集了什么以及不收集什么。这不完全是个坏事：动态语言（例如 Ruby）中的垃圾回收非常的复杂，而程序员最好只关注编写对用户重要的代码。&lt;/p&gt;

&lt;p&gt;但是，偶尔的，你会被 GC 给懵了：它可能运行得太频繁或者又不够，也可能是你的程序使用了大量的内存但你却不知为何。也可能你只是想知道 GC 是如何工作的。&lt;/p&gt;

&lt;p&gt;我们学习 CRuby（用 C 写的标准 Ruby 运行时）关于垃圾回收的一个方法是看看它内建的&lt;code&gt;GC&lt;/code&gt;模块。如果你还没读过&lt;a href="https://ruby-doc.org/core-2.4.0/GC.html" rel="nofollow" target="_blank" title=""&gt;这个模块的文档&lt;/a&gt;，那得读一下，它有几个有意思的方法。但现在，我们只看这一个方法：&lt;code&gt;GC.stat&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GC.stat&lt;/code&gt;输出一组不同数字的哈希，但它们并没有被良好的文档描述，并且其中一些非常容易让人混淆，除非你阅读了相关的 C 代码！不过我帮你读了，现在一起看看&lt;code&gt;GC.stat&lt;/code&gt;提供的信息吧。&lt;/p&gt;

&lt;p&gt;这是我在一个用 Ruby-2.4.0 刚刚启动的&lt;code&gt;irb&lt;/code&gt;会话中执行&lt;code&gt;GC.stat&lt;/code&gt;的输出：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_allocated_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_sorted_length&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_allocatable_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_available_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;25679&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_live_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;25506&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_free_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_final_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_marked_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17773&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_eden_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_tomb_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_allocated_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_freed_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_allocated_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;133299&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_freed_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;107793&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:malloc_increase_bytes&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;45712&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:malloc_increase_bytes_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;16777216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:minor_gc_count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:major_gc_count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:remembered_wb_unprotected_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:remembered_wb_unprotected_objects_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;352&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:old_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:old_objects_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;29670&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:oldmalloc_increase_bytes&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;46160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:oldmalloc_increase_bytes_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;16777216&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注：Ruby-2.5.1 的输出是一样的&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK，很多东西，这有 25 个&lt;em&gt;没有文档&lt;/em&gt;的键值。&lt;/p&gt;

&lt;p&gt;首先，我们看看 &lt;code&gt;GC counts&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="ss"&gt;:minor_gc_count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:major_gc_count&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这几个非常直接。&lt;code&gt;minor_gc_count&lt;/code&gt;和&lt;code&gt;major_gc_count&lt;/code&gt;是 Ruby 进程启动之后的各类型 GC 运行次数。万一你不知道，自从 Ruby-2.1 开始就有个&lt;em&gt;2&lt;/em&gt;种垃圾回收，major 和 minor。minor GC 只尝试回收「新」的对象——存活时间小于等于 3 次 GC 周期。而 major GC 会尝试回收&lt;em&gt;所有&lt;/em&gt;对象，甚至是存活时间超过 3 次 GC 周期。&lt;code&gt;count&lt;/code&gt; = &lt;code&gt;minor_gc_count&lt;/code&gt; + &lt;code&gt;major_gc_count&lt;/code&gt;。如果想了解更多，可以参考我在 FOSDEM 上关于&lt;a href="https://www.youtube.com/watch?v=lcQ-hIfiljA" rel="nofollow" target="_blank" title=""&gt;the history of Ruby Garbage Collection&lt;/a&gt;的讲解。&lt;/p&gt;

&lt;p&gt;出于几个原因跟踪 GC 次数会是有用的。例如，某个特定的后台任务总是触发 GC（以及触发了多少次）。例如，这是一个 Rack 中间件可以记录当服务器处理一个请求时的 GC 次数变化：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GCCounter&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gc_counts_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/count/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="vi"&gt;@app.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gc_counts_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/count/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;gc_counts_before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc_counts_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;va&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;va&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;vb&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的服务是多线程的，那么这个数据不会 100% 的准确，因为其他的线程弄出来的内存压力也可能触发 GC，但这是一个入口。&lt;/p&gt;

&lt;p&gt;现在，我们继续看&lt;code&gt;堆数量&lt;/code&gt;（&lt;code&gt;heap numbers&lt;/code&gt;）的统计：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Page numbers&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_allocated_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_sorted_length&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_allocatable_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;# Slots&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_available_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;25679&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_live_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;25506&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_free_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_final_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_marked_slots&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17773&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;# Eden and Tomb&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_eden_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:heap_tomb_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里，&lt;code&gt;堆heap&lt;/code&gt;是一个 C 数据结构，有时也称为&lt;code&gt;对象空间ObjectSpace&lt;/code&gt;，在其中我们保持了对当前所有活 Ruby 对象的引用，每一个 堆 heap &lt;em&gt;页面 page&lt;/em&gt; 包含了大约 408 个&lt;em&gt;槽 slots&lt;/em&gt;，每个 槽 slot 包含了一个活的 Ruby 对象的信息。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先，你得到了整个 Ruby 对象空间的总大小信息。&lt;code&gt;heap_allocated_pages&lt;/code&gt;是当前已分配的堆空间的数字，这些页面 pages 可能完全是空的、完全满的、或者是中间状态。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;heap_sorted_length&lt;/code&gt;是内存中堆的实际大小 —— 如果我们有 10 个堆页面，然后 free 了中间某个页面，那么 堆页面 的 &lt;code&gt;长度length&lt;/code&gt; 仍然是 10（因为我们没法在内存中移动页面）。&lt;code&gt;heap_sorted_length&lt;/code&gt;总是大于等于实际分配的页面数。&lt;/li&gt;
&lt;li&gt;最后，&lt;code&gt;heap_allocatable_pages&lt;/code&gt; —— 这是 Ruby 当前拥有的 堆页面大小 的一些（已经&lt;code&gt;分配的 malloced&lt;/code&gt;）内存块，我们可以分配一个新的堆页面。如果 Ruby 需要为增加的对象分配新的堆页面，那就会首先使用这个空间。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK，现在我们拿到了一堆和单个对象的&lt;em&gt;槽 slots&lt;/em&gt;有关的数字。&lt;code&gt;heap_available_slots&lt;/code&gt;，很明显是堆页面中所有槽的数量 —— &lt;code&gt;GC.stat[:heap_allocated_pages]&lt;/code&gt; 恒等于 &lt;code&gt;GC.stat[:heap_available_slots]&lt;/code&gt; / &lt;code&gt;GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]&lt;/code&gt;。然后：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;heap_live_slots&lt;/code&gt;是活跃对象数；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;heap_free_slots&lt;/code&gt;是堆页面中空的槽；&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;heap_final_slots&lt;/code&gt;是被&lt;em&gt;析构函数 finalizers&lt;/em&gt;附着了的对象槽。析构函数是 Ruby 中一类朦胧的特性 —— 它们是对象将被释放时运行的 Procs。例如：&lt;code&gt;ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;heap_marked_slots&lt;/code&gt;几乎是&lt;em&gt;旧对象&lt;/em&gt;（存活超过 3 次 GC 周期的对象）的数量加上&lt;em&gt;写屏障无保护对象 write barrier unprotected objects&lt;/em&gt;（一会细说）的数量。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;注：上面两段列表，在原文中是段落形式，但是弄出列表形式似乎好点。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;至于 &lt;code&gt;GC.stat&lt;/code&gt; 中槽数量的实际使用，如果你遇到内存膨胀问题的话我建议监控&lt;code&gt;heap_free_slots&lt;/code&gt;。大量的空对象槽（free slots???）通常预示着你有几个 actions 分配了大量的对象然后又释放了它们，这会不停地增加你的 Ruby 进程的内存。若想了解修复这问题的更多技巧，&lt;a href="http://confreaks.tv/videos/rubyconf2016-halve-your-memory-usage-with-these-12-weird-tricks" rel="nofollow" target="_blank" title=""&gt;参考我在 Rubyconf 上关于 Ruby 内存问题的演讲&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;现在，我们有&lt;code&gt;tomb_pages&lt;/code&gt;和&lt;code&gt;eden_pages&lt;/code&gt;，&lt;code&gt;eden_pages&lt;/code&gt;是包含&lt;em&gt;至少一个&lt;/em&gt;活对象的堆页面。&lt;code&gt;tomb_pages&lt;/code&gt;&lt;em&gt;不包含活对象&lt;/em&gt;，所以有完全空的对象槽 (free slots???)。Ruby 运行时&lt;em&gt;只释放 tomb_pages 返回操作系统&lt;/em&gt;，而&lt;code&gt;eden_pages&lt;/code&gt;则永远不被 free。&lt;/p&gt;

&lt;p&gt;简单的，还有几个&lt;strong&gt;累积的 已分配/已 free 数字&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:total_allocated_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_freed_pages&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_allocated_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;133299&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:total_freed_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;107793&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些数字是进程整个生命周期的&lt;em&gt;累积&lt;/em&gt;值 —— 它们不会被重置、减小。根据它们的变量名称就已经很好理解了。&lt;/p&gt;

&lt;p&gt;最后，我们还有 &lt;strong&gt;垃圾回收阈值 garbage collection thresholds&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:malloc_increase_bytes&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;45712&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:malloc_increase_bytes_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;16777216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:remembered_wb_unprotected_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:remembered_wb_unprotected_objects_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;352&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:old_objects&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;17221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:old_objects_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;29670&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:oldmalloc_increase_bytes&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;46160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:oldmalloc_increase_bytes_limit&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;16777216&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;呃，Ruby 开发者的一个主要误解是 GC*何时*被触发。我们可以通过&lt;code&gt;GC.start&lt;/code&gt;手动触发 GC，但这不发生于产品线。一些人觉得 GC 是根据某种定时器（例如每隔几秒或几个请求）来运行的，事实并非如此。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;minor GC 是当缺少空槽时被触发的。Ruby 不会自动 GC 任何对象 —— 它只当空间不足时运行回收。所以当没有&lt;code&gt;free_slots&lt;/code&gt;剩下时，Ruby 运行 minor GC —— 标记并且清除所有新对象和&lt;em&gt;记忆集&lt;/em&gt;中&lt;em&gt;未被写屏障保护&lt;/em&gt;的对象。这些术语随后有解释。&lt;/li&gt;
&lt;li&gt;major GC 将在 minor GC 运行后仍然缺少空槽时被触发，或者是以下 4 个阈值中任何一个被突破了：

&lt;ol&gt;
&lt;li&gt;oldmalloc&lt;/li&gt;
&lt;li&gt;malloc&lt;/li&gt;
&lt;li&gt;old object count&lt;/li&gt;
&lt;li&gt;「shady」/「写屏障未保护数」&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;GC.stat&lt;/code&gt; 包含了这四个阈值（限制）和运行时的当前状态。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;malloc_increase_bytes&lt;/code&gt; 指的是 Ruby 为「堆（我们已经讨论过的）」&lt;em&gt;外&lt;/em&gt; 对象分配的空间。堆页面里的每一个对象槽只有 40 字节（参考&lt;code&gt;GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]&lt;/code&gt;），那当对象大小超过 40 字节（比如长字符串）时会发生什么呢？我们为这个对象在其他任何地方&lt;code&gt;malloc&lt;/code&gt;空间！如果我们为一个字符串分配了 80 字节的空间，那 &lt;code&gt;malloc_increase_bytes&lt;/code&gt; 就将增加 80。当这个数值抵达了限制，就触发了一次 major GC。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oldmalloc_increase_bytes&lt;/code&gt; 和 &lt;code&gt;malloc_increase_bytes&lt;/code&gt; 类似，但只针对 &lt;em&gt;old&lt;/em&gt; 对象。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remembered_wb_unprotected_objects&lt;/code&gt; 是&lt;em&gt;已记忆集 remembered set&lt;/em&gt;中的部分但没有被&lt;em&gt;写屏障 write-barrier&lt;/em&gt;保护 的对象数量。

&lt;ol&gt;
&lt;li&gt;写屏障是一个简单的 Ruby 运行时和对象之间的接口，能让我们在对象被创建时追踪它的引用和被引用。C 扩展可以不经过写屏障而创建到对象的引用，所以被 C 扩展引用了的对象被称为「shady」或者「写屏障未保护的」。&lt;/li&gt;
&lt;li&gt;已记忆集是拥有引用&lt;em&gt;新 new&lt;/em&gt;对象的&lt;em&gt;老 old&lt;/em&gt;对象的列表集合。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;old_objects&lt;/code&gt; 被标记为 老 对象槽的数量。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你的问题是 major GC 的次数过多，那追踪这些阈值可能对 debug 有帮助。&lt;/p&gt;

&lt;p&gt;我希望这是对 GC.stat 的教育看法 —— 它是个信息丰富的哈希，能用来在需要修复不良 GC 行为时构建临时的 debug 方案。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译注：添加了一个 &lt;a href="https://github.com/blacklee/RubyGC-CN/blob/master/gc+grouped_stat.rb" rel="nofollow" target="_blank" title=""&gt;gc+grouped_stat.rb&lt;/a&gt; 文件，可以简单看看&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr&gt;

&lt;p&gt;不是非常有把握的翻译：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;malloc_increase_bytes&lt;/code&gt; refers to when Ruby allocates space for objects *outside *of the “heap” we’ve been discussing so far&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;malloc_increase_bytes&lt;/code&gt; 指的是 Ruby 为「堆（我们已经讨论过的）」&lt;em&gt;外&lt;/em&gt; 对象分配的空间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;remembered_wb_unprotected_objects&lt;/code&gt; is a count of objects which are not protected by the &lt;code&gt;write-barrier&lt;/code&gt; and are part of the remembered set&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remembered_wb_unprotected_objects&lt;/code&gt; 是&lt;em&gt;已记忆集 remembered set&lt;/em&gt;中的部分但没有被&lt;em&gt;写屏障 write-barrier&lt;/em&gt;保护 的对象数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3.

&lt;ul&gt;
&lt;li&gt;The write-barrier is simply a interface between the Ruby runtime and an object, so that we can track references to and from the object when they’re created&lt;/li&gt;
&lt;li&gt;写屏障是一个简单的 Ruby 运行时和对象之间的接口，能让我们在对象被创建时追踪它的引用和被引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.

&lt;ul&gt;
&lt;li&gt;The part of GC.stat we’re looking at here shows each of those four thresholds (the limit) and the current state of the runtime on the way to that threshold.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GC.stat&lt;/code&gt; 包含了这四个阈值（限制）和运行时的当前状态&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>blacklee</author>
      <pubDate>Wed, 09 Jan 2019 10:09:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/37982</link>
      <guid>https://ruby-china.org/topics/37982</guid>
    </item>
    <item>
      <title>Debugging memory leaks in Ruby</title>
      <description>&lt;h2 id="Debugging memory leaks in Ruby"&gt;Debugging memory leaks in Ruby&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Origin Post: &lt;a href="https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby" rel="nofollow" target="_blank"&gt;https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文 [创作时间 2015-3-31]: &lt;a href="https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby" rel="nofollow" target="_blank"&gt;https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/blacklee/RubyGC-CN/blob/master/debugging-memory-leaks-in-ruby.md" rel="nofollow" target="_blank" title=""&gt;译文 github 地址&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：带有 (???) 表明我对此句的翻译拿捏不准。水平有限。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;本文是关于诊断并修复 Ruby 内存泄漏的一些工具、提示和技术。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在每一个 Rails 开发者的生活中的某个时刻他一定会遇到内存泄漏的问题。内存也许是稳定的小幅增长，也许是任务队列中某些任务执行时的井喷增长。&lt;/p&gt;

&lt;p&gt;可悲的是，大多数 Ruby 开发者仅仅是简单的采用了&lt;a href="http://mmonit.com/monit/" rel="nofollow" target="_blank" title=""&gt;monit&lt;/a&gt;, &lt;a href="https://github.com/mperham/inspeqtor" rel="nofollow" target="_blank" title=""&gt;inspeqtor&lt;/a&gt;或者&lt;a href="https://github.com/kzk/unicorn-worker-killer" rel="nofollow" target="_blank" title=""&gt;unicorn worker killers&lt;/a&gt;，这可以让人暂时忽略这个问题，从而去做些更&lt;strong&gt;重要&lt;/strong&gt;的事。&lt;/p&gt;

&lt;p&gt;不幸的是，这种处理方法导致了一些不好的副作用。除了效率不高，不稳定、需要更多内存让社区对 Ruby 缺乏信心。监控、重启进程是你武器库中很重要的工具，但它充其量只是权宜之计和保障，它不是个解决方案。&lt;/p&gt;

&lt;p&gt;我们有一些极好的工具可以用来处理内存泄漏，&lt;strong&gt;特别&lt;/strong&gt;是简单的内存泄漏：&lt;strong&gt;managed memory leak&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="Are we leaking? 真的有泄漏？"&gt;Are we leaking? 真的有泄漏？&lt;/h2&gt;
&lt;p&gt;第一也是&lt;strong&gt;最重要&lt;/strong&gt;的处理内存问题的步骤是用图表来监控内存使用情况。在&lt;a href="http://www.discourse.org/" rel="nofollow" target="_blank" title=""&gt;Discourse&lt;/a&gt;里我们使用了&lt;a href="http://graphite.wikidot.com/" rel="nofollow" target="_blank" title=""&gt;Graphite&lt;/a&gt;, &lt;a href="https://github.com/etsy/statsd/" rel="nofollow" target="_blank" title=""&gt;statsd&lt;/a&gt;和&lt;a href="http://grafana.org/" rel="nofollow" target="_blank" title=""&gt;Grafana&lt;/a&gt;这一系列的组合工具来图表化应用程序的各项指标。&lt;/p&gt;

&lt;p&gt;前一阵子我为此工作打包了一个 &lt;a href="https://github.com/SamSaffron/graphite_docker" rel="nofollow" target="_blank" title=""&gt;Docker 镜像文件&lt;/a&gt;，它和我们目前正在运用的工具非常类似。如果不想自己造轮子，你可以看看&lt;a href="http://newrelic.com/" rel="nofollow" target="_blank" title=""&gt;New Relic&lt;/a&gt;, &lt;a href="https://www.datadoghq.com/" rel="nofollow" target="_blank" title=""&gt;Datadog&lt;/a&gt;或者其他基于云服务的度量提供者。你首先需要追踪的关键指标是 Ruby 进程的 RSS（&lt;a href="https://en.wikipedia.org/wiki/Resident_set_size" rel="nofollow" target="_blank" title=""&gt;Resident set size&lt;/a&gt;: 实际使用物理内存 (包含共享库占用的内存)）。在 Discourse 中我们观察 Web 服务器&lt;a href="http://unicorn.bogomips.org/" rel="nofollow" target="_blank" title=""&gt;Unicorn&lt;/a&gt;和任务队列&lt;a href="http://sidekiq.org/" rel="nofollow" target="_blank" title=""&gt;Sidekiq&lt;/a&gt;的最大 RSS 数值。&lt;/p&gt;

&lt;p&gt;Discourse 被用多个 Docker 容器部署在多台机器上。我们使用了定制的&lt;a href="https://github.com/discourse/discourse_docker/tree/03b50438d73dbe6076a5a4179e336afaef2b28c2/image/monitor" rel="nofollow" target="_blank" title=""&gt;Docker 容器&lt;/a&gt;来监控所有其他 Docker 容器。这个定制的容器启动后能访问 Docker 套接字 (???)，所以它能够询问 Docker 关于 Docker 的信息。它使用了&lt;code&gt;docker exec&lt;/code&gt;来得到容器内运行的所有进程的所有类别的信息。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注：Discourse 使用了 unicorn 主进程来启动多个子 workers 和任务队列，这不可能由单进程单容器的方式达成（在分支间共享内存）(???)。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;利用此信息，我们就能轻松的为任何机器任何容器绘制内存占用 (RSS) 趋势图了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://discuss.samsaffron.com/uploads/default/_optimized/dc2/faf/6a0f49a725_690x273.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;长期图表对任何内存泄漏分析都很&lt;strong&gt;决定性的&lt;/strong&gt;，它让我们能够看到问题何时发生，内存的增长率和增长图形。&lt;strong&gt;进程不稳定吗？它和某个任务执行有关系吗？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当处理与 C 扩展有关的内存泄漏时，有此信息是决定性的。孤立的 C 扩展内存泄漏通常牵涉到&lt;a href="http://valgrind.org/" rel="nofollow" target="_blank" title=""&gt;valgrind&lt;/a&gt;和自行编译的支持用 valgrind 进行 debug 的 Ruby 版本。这是个极其困难的工作，不到最后我们都不愿意涉及它。在&lt;a href="https://github.com/eventmachine/eventmachine/pull/586" rel="nofollow" target="_blank" title=""&gt;升级 EventMachine&lt;/a&gt;后，隔离趋势开始变得更加简单 (???)。&lt;/p&gt;
&lt;h2 id="Managed memory leaks"&gt;Managed memory leaks&lt;/h2&gt;
&lt;p&gt;不同于 unmanaged 的泄漏，处理 managed 的泄漏很直接。Ruby2.1+ 的新工具让调试这些泄漏很简单。&lt;/p&gt;

&lt;p&gt;Ruby2.1+ 里我们能做的最棒的事是爬取进程的对象空间 (object space)，做一个快照，等待一会，再做一个快照，然后进行对比。对此我有一个简单的实现，它在 Discourse 里的&lt;a href="https://github.com/discourse/discourse/blob/586cca352d1bb2bb044442d79a6520c9b37ed1ae/lib/memory_diagnostics.rb" rel="nofollow" target="_blank" title=""&gt;MemoryDiagnositics&lt;/a&gt;，不过让它正确工作却需要些小诡计。当做快照的时候你需要 fork 你的进程，如此以不干涉正在运行的进程，你能收集的信息非常简单。我们能断定某些对象泄漏了，但我们无法确定它们是在哪里被分配的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3377 objects have leaked
Summary:
String: 1703
Bignum: 1674

Sample Items:
Bignum: 40 bytes
Bignum: 40 bytes
String: 
Bignum: 40 bytes
String: 
Bignum: 40 bytes
String: 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果我们够幸运，可以得到一些泄漏了的 number 和 string，这些的揭露足以帮助我们弄清它们。&lt;/p&gt;

&lt;p&gt;另外我们还有&lt;code&gt;GC.stat&lt;/code&gt;可以告诉我们现在有多少存活的对象及其他信息。&lt;/p&gt;

&lt;p&gt;这个信息十分有限，我们能断定发生了内存泄漏，但查找原因则十分困难。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注：一个很有意思的度量标准是&lt;code&gt;GC.stat[:heap_live_slots]&lt;/code&gt;，通过这个信息，我们能简单的判断现在有一个 managed 对象泄漏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Managed heap dumping"&gt;Managed heap dumping&lt;/h2&gt;
&lt;p&gt;Ruby2.1 引入了&lt;a href="http://tmm1.net/ruby21-objspace/" rel="nofollow" target="_blank" title=""&gt;heap dumping&lt;/a&gt;，如果你启用了分配跟踪 (allocation tracing) 你将得到一些非常有意思的信息。&lt;/p&gt;

&lt;p&gt;收集堆导出的方法非常简单&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;开启分配跟踪&lt;/strong&gt; &lt;strong&gt;Turn on allocation tracing&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'objspace'&lt;/span&gt;
&lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace_object_allocations_start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会&lt;strong&gt;显著的&lt;/strong&gt;让你的程序变慢并且导致占用更多的内存。不过，这是收集有用信息的钥匙，而且之后可以关闭。我在分析时会在启动程序之后马上运行它。&lt;/p&gt;

&lt;p&gt;我最后一次调试 Discourse 的 Sidekiq 内存问题时我在一台空闲机器上部署了额外的 Docker 镜像，这给了我完全的自由，不必去担心影响到 SLA（服务级别协议 service-level agreement）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;下一步，等待&lt;/strong&gt; &lt;strong&gt;Next, play the waiting game&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当内存清楚的泄漏后，（你可以观察&lt;code&gt;GC.stat&lt;/code&gt;或者通过测定 RSS 情况），运行：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/tmp/my_dump"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;output: &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Running Ruby in an already running process"&gt;Running Ruby in an already running process&lt;/h2&gt;
&lt;p&gt;要让此方法工作，我们需要在一个已启动的进程内部运行 Ruby 代码。&lt;/p&gt;

&lt;p&gt;幸运的是，&lt;a href="https://github.com/tmm1/rbtrace" rel="nofollow" target="_blank" title=""&gt;rbtrace&lt;/a&gt; gem 允许我们这样做（还有更多），此外在生产环境中运行它也是&lt;strong&gt;&lt;code&gt;安全&lt;/code&gt;&lt;/strong&gt;的。&lt;/p&gt;

&lt;p&gt;我们可以这样强制 Sidekiq 导出它的堆信息：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rbtrace &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$SIDEKIQ_PID&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'Thread.new{GC.start;require "objspace";io=File.open("/tmp/ruby-heap.dump", "w"); ObjectSpace.dump_all(output: io); io.close}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rbtrace 运行在一个限制性的上下文中，一个巧妙的技巧是用&lt;code&gt;Thread.new&lt;/code&gt;来突破陷阱环境 (???)。&lt;/p&gt;

&lt;p&gt;我们也可以用 rbtrace 在进程外部收集信息，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rbtrace &lt;span class="nt"&gt;-p&lt;/span&gt; 6744 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'GC.stat'&lt;/span&gt;
/usr/local/bin/ruby: warning: RUBY_HEAP_MIN_SLOTS is obsolete. Use RUBY_GC_HEAP_INIT_SLOTS instead.
&lt;span class="k"&gt;***&lt;/span&gt; attached to process 6744
&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; GC.stat
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;:count&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;49, :heap_allocated_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;1960, :heap_sorted_length&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;1960, :heap_allocatable_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;12, :heap_available_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;798894, :heap_live_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;591531, :heap_free_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;207363, :heap_final_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;0, :heap_marked_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;335775, :heap_swept_slots&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;463124, :heap_eden_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;1948, :heap_tomb_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;12, :total_allocated_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;1960, :total_freed_pages&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;0, :total_allocated_objects&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;13783631, :total_freed_objects&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;13192100, :malloc_increase_bytes&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;32568600, :malloc_increase_bytes_limit&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;33554432, :minor_gc_count&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;41, :major_gc_count&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;8, :remembered_wb_unprotected_objects&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;12175, :remembered_wb_unprotected_objects_limit&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;23418, :old_objects&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;309750, :old_objects_limit&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;618416, :oldmalloc_increase_bytes&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;32783288, :oldmalloc_increase_bytes_limit&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;44484250&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;***&lt;/span&gt; detached from process 6744
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Analyzing the heap dump"&gt;Analyzing the heap dump&lt;/h3&gt;
&lt;p&gt;当拿到这个丰富的堆信息后我们开始分析，首先要看的报告是每一个 GC 世代对象数量。&lt;/p&gt;

&lt;p&gt;当开启了对象分配追踪后，运行时会为所有对象分配附加丰富的信息，包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;分配给它的 GC 世代 (???)。&lt;/li&gt;
&lt;li&gt;分配该对象的代码位置（文件名 + 代码行）&lt;/li&gt;
&lt;li&gt;一个被修剪了的值&lt;/li&gt;
&lt;li&gt;bytesize&lt;/li&gt;
&lt;li&gt;…………其他&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;导出的文件是 JSON 格式的，每行都可以简单的被解析，例如&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0x7ffc567fbf98"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"STRING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0x7ffc565c4ea0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"frozen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"embedded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fstring"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"bytesize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ensure in dispatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/activesupport-4.1.9/lib/active_support/dependencies.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;247&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"generation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"memsize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"wb_protected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"old"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"long_lived"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"marked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个简单的报告显示了多少对象在每一次的 GC 世代中被保留，这是个非常好的查看内存泄漏的开始，这是对象泄漏的一条时间线。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Analyzer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"generation"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"generation &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; objects &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以我得到的结果为例：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generation  objects 334181
generation 7 objects 6629
generation 8 objects 38383
generation 9 objects 2220
generation 10 objects 208
generation 11 objects 110
generation 12 objects 489
generation 13 objects 505
generation 14 objects 1297
generation 15 objects 638
generation 16 objects 748
generation 17 objects 1023
generation 18 objects 805
generation 19 objects 407
generation 20 objects 126
generation 21 objects 1708
generation 22 objects 369
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们预期在进程启动后和偶尔引用新依赖时持有大量的对象，然而我们并不期望分配一致数量的对象并从不清理它们。让我们详细查看一个特定的世代：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Analyzer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"generation"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generation 19 objects 407
/usr/local/lib/ruby/2.2.0/weakref.rb:87 * 144
/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/therubyracer-0.12.1/lib/v8/weak.rb:21 * 72
/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/therubyracer-0.12.1/lib/v8/weak.rb:42 * 72
/var/www/discourse/lib/freedom_patches/translate_accelerator.rb:65 * 15
/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/i18n-0.7.0/lib/i18n/interpolate/ruby.rb:21 * 15
/var/www/discourse/lib/email/message_builder.rb:85 * 9
/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/actionview-4.1.9/lib/action_view/template.rb:297 * 6
/var/www/discourse/lib/email/message_builder.rb:36 * 6
/var/www/discourse/lib/email/message_builder.rb:89 * 6
/var/www/discourse/lib/email/message_builder.rb:46 * 6
/var/www/discourse/lib/email/message_builder.rb:66 * 6
/var/www/discourse/vendor/bundle/ruby/2.2.0/gems/activerecord-4.1.9/lib/active_record/connection_adapters/postgresql_adapter.rb:515 * 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更进一步，我们可以追踪对象的引用路径来查看谁引用了各种对象，并且重建对象图。&lt;/p&gt;

&lt;p&gt;我在这个特殊情况下注意到的第一件事是我写的代码 (???)，这是 Rails 本地化的猴补丁。&lt;/p&gt;
&lt;h2 id="Why we monkey patch rails localization?"&gt;Why we monkey patch rails localization?&lt;/h2&gt;
&lt;p&gt;在 Discourse 中我们出于 2 个原因对 Rails 本地化子系统进行了猴补丁：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;早期我们发现它很慢，需要更好的性能。&lt;/li&gt;
&lt;li&gt;最近我们开始积累大量的翻译，并且需要确保我们只按需加载翻译以降低内存使用率。 （这节省了我们 20MB 的 RSS）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;考虑下面这个工作：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark/ips'&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../../config/environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在打猴补丁之前&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam@ubuntu discourse % ruby bench.rb
Calculating &lt;span class="nt"&gt;-------------------------------------&lt;/span&gt;
                         4.518k i/100ms
&lt;span class="nt"&gt;-------------------------------------------------&lt;/span&gt;
                        121.230k &lt;span class="o"&gt;(&lt;/span&gt;±11.0%&lt;span class="o"&gt;)&lt;/span&gt; i/s -    600.894k
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在打猴补丁之后&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam@ubuntu discourse % ruby bench.rb
Calculating &lt;span class="nt"&gt;-------------------------------------&lt;/span&gt;
                        22.509k i/100ms
&lt;span class="nt"&gt;-------------------------------------------------&lt;/span&gt;
                        464.295k &lt;span class="o"&gt;(&lt;/span&gt;±10.4%&lt;span class="o"&gt;)&lt;/span&gt; i/s -      2.296M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就是说我们的国际化系统的速度快了 4 倍，但是……它泄漏内存。&lt;/p&gt;

&lt;p&gt;重审代码后我发现了错误的代码行 &lt;a href="https://github.com/discourse/discourse/blob/3c6aede1aa98a8456b00ab2d1e01b3f35466323c/lib/freedom_patches/translate_accelerator.rb#L65" rel="nofollow" target="_blank" title=""&gt;discourse&lt;/a&gt;：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# load it&lt;/span&gt;
    &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_translations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grep&lt;/span&gt; &lt;span class="no"&gt;Regexp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.yml$"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="vi"&gt;@loaded_locales&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@cache&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;LruRedux&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ThreadSafeCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LRU_CACHE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# ----------------------------- 这一行&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@cache.fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;
    &lt;span class="n"&gt;load_locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@loaded_locales.include&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate_no_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;MissingInterpolationArgument&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;locale: &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate_no_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果，我们从包含 ActiveRecord 对象的电子邮件消息构建器发送哈希值 (???)，这个哈希随后被用作缓存的键值，而此缓存允许 2000 项条目。考虑到每个条目都可能涉及到大量的 ActiveRecord 对象，内存泄漏就非常严重。&lt;/p&gt;

&lt;p&gt;为减轻内存压力，我更改了键值的构成策略，压缩了缓存并且完全绕过复杂的本地化：&lt;a href="https://github.com/discourse/discourse/commit/830ce05fe64fd310d26d7da87ea6e4076696b7c8#diff-27e12e440c4032dade41a92dae381e51" rel="nofollow" target="_blank" title=""&gt;pull request&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一天之后再看内存图表可以轻松观察到此更改的影响&lt;/p&gt;

&lt;p&gt;&lt;img src="https://discuss.samsaffron.com/uploads/default/_optimized/ea3/b24/d4e46ac88e_690x257.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;虽然没有阻止内存的泄漏，但很明确，泄漏速度慢下来了。&lt;/p&gt;
&lt;h3 id="therubyracer is leaking"&gt;therubyracer is leaking&lt;/h3&gt;
&lt;p&gt;在我们列表顶部我们看见 JavaScript 引擎&lt;a href="https://github.com/cowboyd/therubyracer" rel="nofollow" target="_blank" title=""&gt;therubyracer&lt;/a&gt;泄漏了很多对象，特别是它使用弱引用去维持 Ruby 到 JavaScript 的映射被持有太久了。&lt;/p&gt;

&lt;p&gt;为了保持 Discourse 将 Markdown 转换为 HTML 的性能，我们保留了 JavaScript 引擎上下文。该引擎的启动太耗资源，所以当我们编辑帖子时我们在内存总保留了它 (???)。&lt;/p&gt;

&lt;p&gt;由于我们的代码相当孤立，因此 repro(reproduce?) 是微不足道的，首先我们用&lt;a href="https://github.com/SamSaffron/memory_profiler" rel="nofollow" target="_blank" title=""&gt;memory_profiler&lt;/a&gt;gem 看看我们泄漏了多少对象。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;NV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'memory_profiler'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../../config/environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# warmup&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;PrettyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="no"&gt;MemoryProfiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;PrettyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pretty_print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在报告的顶部可以看到：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retained objects by location
-----------------------------------
       901  /home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/2.1.0/weakref.rb:87
       901  /home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/therubyracer-0.12.1/lib/v8/weak.rb:21
       600  /home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/therubyracer-0.12.1/lib/v8/weak.rb:42
       250  /home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/therubyracer-0.12.1/lib/v8/context.rb:97
        50  /home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/therubyracer-0.12.1/lib/v8/object.rb:8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以我们每次编辑一个帖子就泄漏了 54（(901+901+600+250+50)/50.times）个对象，这增长太快了。我们还可能在这里泄漏了 unmanaged 的内存，这复杂化了问题。&lt;/p&gt;

&lt;p&gt;由于我们有代码行，所以很简单的就追踪到了泄漏的位置&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'weakref'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Ref&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeakRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;object&lt;/span&gt;
    &lt;span class="vi"&gt;@ref.__getobj__&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeakRef&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RefError&lt;/span&gt;
    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeakValueMap&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
      &lt;span class="vi"&gt;@values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;V8&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Weak&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个&lt;code&gt;WeakValueMap&lt;/code&gt;对象保持永远增长并且它的对象不会被清理。使用&lt;code&gt;WeakRef&lt;/code&gt;的目的是为确保我们允许当对象不被引用时会被清理掉。麻烦是对此封装的引用现在保持在 JavaScript 上下文的整个生命期。&lt;/p&gt;

&lt;p&gt;修复很直接：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeakValueMap&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;V8&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Weak&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define_finalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensure_cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensure_cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在被封装的对象上定义了一个析构函数，以确保我们清理这些被封装的对象，保持&lt;code&gt;WeakValueMap&lt;/code&gt;小一点。&lt;/p&gt;

&lt;p&gt;效果惊人：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RAILS_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'objspace'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'memory_profiler'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../../config/environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rss&lt;/span&gt;
 &lt;span class="sb"&gt;`ps -eo pid,rss | grep &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | awk '{print $2}'`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;PrettyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# MemoryProfiler has a helper that runs the GC multiple times to make sure all objects that can be freed are freed.&lt;/span&gt;
&lt;span class="c1"&gt;# MemoryProfiler 有一个辅助方法会运行GC很多次，确保所有能被释放的对象都释放掉&lt;/span&gt;
&lt;span class="no"&gt;MemoryProfiler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_gc&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"rss: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; live objects &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:heap_live_slots&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;PrettyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;MemoryProfiler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_gc&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"rss: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; live objects &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:heap_live_slots&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化之前&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rss: 137660 live objects 306775
rss: 259888 live objects 570055
rss: 301944 live objects 798467
rss: 332612 live objects 1052167
rss: 349328 live objects 1268447
rss: 411184 live objects 1494003
rss: 454588 live objects 1734071
rss: 451648 live objects 1976027
rss: 467364 live objects 2197295
rss: 536948 live objects 2448667
rss: 600696 live objects 2677959
rss: 613720 live objects 2891671
rss: 622716 live objects 3140339
rss: 624032 live objects 3368979
rss: 640780 live objects 3596883
rss: 641928 live objects 3820451
rss: 640112 live objects 4035747
rss: 722712 live objects 4278779
/home/sam/Source/discourse/lib/pretty_text.rb:185:in `block in markdown': Script Timed Out (PrettyText::JavaScriptError)
    from /home/sam/Source/discourse/lib/pretty_text.rb:350:in `block in protect'
    from /home/sam/Source/discourse/lib/pretty_text.rb:348:in `synchronize'
    from /home/sam/Source/discourse/lib/pretty_text.rb:348:in `protect'
    from /home/sam/Source/discourse/lib/pretty_text.rb:161:in `markdown'
    from /home/sam/Source/discourse/lib/pretty_text.rb:218:in `cook'
    from tmp/mem_leak.rb:30:in `block (2 levels) in &amp;lt;main&amp;gt;'
    from tmp/mem_leak.rb:29:in `times'
    from tmp/mem_leak.rb:29:in `block in &amp;lt;main&amp;gt;'
    from tmp/mem_leak.rb:27:in `times'
    from tmp/mem_leak.rb:27:in `&amp;lt;main&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化之后&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rss: 137556 live objects 306646
rss: 259576 live objects 314866
rss: 261052 live objects 336258
rss: 268052 live objects 333226
rss: 269516 live objects 327710
rss: 270436 live objects 338718
rss: 269828 live objects 329114
rss: 269064 live objects 325514
rss: 271112 live objects 337218
rss: 271224 live objects 327934
rss: 273624 live objects 343234
rss: 271752 live objects 333038
rss: 270212 live objects 329618
rss: 272004 live objects 340978
rss: 270160 live objects 333350
rss: 271084 live objects 319266
rss: 272012 live objects 339874
rss: 271564 live objects 331226
rss: 270544 live objects 322366
rss: 268480 live objects 333990
rss: 271676 live objects 330654
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修复后看起来内存稳定，活动对象数稳定。相关的&lt;a href="https://github.com/cowboyd/therubyracer/pull/336" rel="nofollow" target="_blank" title=""&gt;pull request&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;Ruby 现有的工具提供了了查看 Ruby 运行时的极佳可视性，围绕这种新设施的工具还在改进中但仍然相当粗糙。&lt;/p&gt;

&lt;p&gt;作为一个之前生涯都是.NET 的程序员我真的很想念那优异的&lt;a href="http://memprofiler.com/" rel="nofollow" target="_blank" title=""&gt;内存探查&lt;/a&gt;工具，幸运的是我们现在有创建此类工具所有需要的信息。&lt;/p&gt;

&lt;p&gt;祝你在捕捉内存泄漏时好运，我希望本文能帮助你，&lt;strong&gt;请&lt;/strong&gt;在下次部署&lt;strong&gt;unicorn OOM killer&lt;/strong&gt;时三思。&lt;/p&gt;

&lt;p&gt;非常感谢&lt;a href="http://www.atdot.net/~ko1/" rel="nofollow" target="_blank" title=""&gt;Koichi Sasada&lt;/a&gt;和&lt;a href="http://tmm1.net/" rel="nofollow" target="_blank" title=""&gt;Aman Gupta&lt;/a&gt;为我们创造了新的内存探测基础工具。&lt;/p&gt;

&lt;p&gt;PS：另一个值得阅读的优秀资源是 Oleg Dashevskii 的&lt;a href="http://www.be9.io/2015/09/21/memory-leak/" rel="nofollow" target="_blank" title=""&gt;How I spent two weeks hunting a memory leak in Ruby&lt;/a&gt;。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;不是非常有把握的翻译：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.

&lt;ul&gt;
&lt;li&gt;[managed/unmanaged] memory leak&lt;/li&gt;
&lt;li&gt;保留的英文原文，「托管/非托管」的内存泄漏？是说的「自己代码」和「第三方代码」？还是说「Ruby 代码」和「C 扩展代码」？拿不准，就保留原文了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.

&lt;ul&gt;
&lt;li&gt;This container is launched with access to the Docker socket so it can interrogate Docker about Docker.&lt;/li&gt;
&lt;li&gt;这个定制的容器启动后能访问 Docker 套接字，所以它能够询问 Docker 关于 Docker 的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3.

&lt;ul&gt;
&lt;li&gt;it is impossible to achieve the same setup (which shares memory among forks) in a one container per process world.&lt;/li&gt;
&lt;li&gt;这不可能由单进程单容器的方式达成（在分支间共享内存）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.

&lt;ul&gt;
&lt;li&gt;It is much simpler to isolate that a trend started after upgrading EventMachine to version 1.0.5.&lt;/li&gt;
&lt;li&gt;在&lt;a href="https://github.com/eventmachine/eventmachine/pull/586" rel="nofollow" target="_blank" title=""&gt;升级 EventMachine&lt;/a&gt;后，隔离趋势开始变得更加简单。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5.

&lt;ul&gt;
&lt;li&gt;a nifty trick is breaking out of the trap context with Thread.new&lt;/li&gt;
&lt;li&gt;一个巧妙的技巧是用&lt;code&gt;Thread.new&lt;/code&gt;来突破陷阱环境&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6.

&lt;ul&gt;
&lt;li&gt;The GC generation it was allocated in&lt;/li&gt;
&lt;li&gt;分配给它的 GC 世代&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;7.

&lt;ul&gt;
&lt;li&gt;The first thing I attacked in this particular case was code I wrote, which is a monkey patch to Rails localization.&lt;/li&gt;
&lt;li&gt;我在这个特殊情况下注意到的第一件事是我写的代码，这是 Rails 本地化的猴子补丁。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8.

&lt;ul&gt;
&lt;li&gt;we were sending a hash in from the email message builder that includes ActiveRecord objects&lt;/li&gt;
&lt;li&gt;我们从包含 ActiveRecord 对象的电子邮件消息构建器发送哈希值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9.

&lt;ul&gt;
&lt;li&gt;so we keep it in memory plugging in new variables as we bake posts.&lt;/li&gt;
&lt;li&gt;所以当我们编辑帖子时我们在内存总保留了它&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;10.

&lt;ul&gt;
&lt;li&gt;Running Ruby in an already running process&lt;/li&gt;
&lt;li&gt;这一句根据上下文我读出来的是类似「attach to a running Ruby process and debug」，不知道怎么翻译原文&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>blacklee</author>
      <pubDate>Mon, 17 Dec 2018 11:52:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/37907</link>
      <guid>https://ruby-china.org/topics/37907</guid>
    </item>
    <item>
      <title>遇到一个垃圾回收的问题，有 CentOS 服务器的朋友帮忙确认一下？</title>
      <description>&lt;p&gt;非常简单的一个脚本，运行两次看结果&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Numeric&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_hmsize&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_hmtime&lt;/span&gt;
    &lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="n"&gt;sec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;m"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;sec&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sec&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"../wiki_pages.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="mi"&gt;700000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;pageid: &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"title of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;pagename: &lt;/span&gt;&lt;span class="s2"&gt;"pagename_of_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"created a json file: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_hmsize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"init the json file at the first launch, please run again."&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;RUBY_DESCRIPTION&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'objspace'&lt;/span&gt;

&lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;sys_mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`ps -o rss= -p &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_hmsize&lt;/span&gt;
    &lt;span class="n"&gt;objspace_mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memsize_of_all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_hmsize&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_hmtime&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;sys_mem:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sys_mem&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, objspace_mem:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;objspace_mem&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="no"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"read content from file &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_hmsize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"parse json string to object"&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"set string&amp;amp;object to nil, and sleep 30 min to check the memory usage."&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;

&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我测试出来的结果是，在 Mac 上，进程的内存占用以 12M 开始，峰值 400M，30 分钟后退出时 45M 左右。但是在 CentOS 上，垃圾回收到 280M 就不会再下降了。&lt;/p&gt;

&lt;p&gt;Ruby 版本 2.5.1 和 2.5.3 的结果一样，Ruby 是用 rvm 安装的。&lt;/p&gt;

&lt;p&gt;准备给开发组报 bug 了，谁有空先帮忙验证一下。&lt;/p&gt;</description>
      <author>blacklee</author>
      <pubDate>Mon, 10 Dec 2018 18:41:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/37882</link>
      <guid>https://ruby-china.org/topics/37882</guid>
    </item>
  </channel>
</rss>
