Ruby内存泄漏(MRI)

时间:2016-12-02 22:17:43

标签: ruby memory-leaks

我必须遗漏一些东西,但我在Ruby中编写的每个应用程序似乎都在泄漏一些内存。我使用Ruby MRI 2.3,但我看到与其他版本相同的行为。

每当我编写一个在循环中执行某些操作的测试应用程序时,它就会慢慢泄漏内存。

while true
   #do something
   sleep 0.1
end

例如,我可以写入数组然后在循环中清理它,或者只发送http post请求。

这里只是一个例子,但我有很多这样的例子:

require 'net/http'
require 'json'
require 'openssl'

class Tester

    def send_http some_json
        begin
            @uri = URI('SERVER_URL')
            @http = Net::HTTP.new(@uri.host, @uri.port)
            @http.use_ssl = true
            @http.keep_alive_timeout = 10
            @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
            @http.read_timeout = 30
            @req = Net::HTTP::Post.new(@uri.path, 'Content-Type' => 'application/json')
            @req.body = some_json.to_json
            res = @http.request(@req)
        rescue Exception => e  
                puts e.message  
                puts e.backtrace.inspect  
        end
    end

    def run
        while true
            some_json = {"name": "My name"}
            send_http(some_json)
            sleep 0.1
        end
    end
end


Tester.new.run

我看到的泄漏非常小,每小时可以达到0.5 mb。

我使用MemoryProfiler和GC :: Profiler.enable运行代码,它显示我没有泄漏。所以它必须是2个选项:

  1. C代码中存在内存泄漏。这可能是可能的,但我不使用任何外部宝石,因此我发现很难相信Ruby正在泄漏。

  2. 没有内存泄漏,这是某种Ruby内存管理机制。事情是,我可以挑衅地看到记忆在增长。它什么时候会增长?我需要等多少才能知道它是泄漏还是现在?

  3. 相同的代码与JRuby完全正常运行,没有任何泄漏。

    我很惊讶地读了一篇文章:

    stack overlflow 来自Joe Edgar:

      

    Ruby的历史主要是作为文本处理的命令行工具   因此它重视快速启动和小内存占用。它   不是为长时间运行的守护程序/服务器进程设计的

    如果写的是真的,Ruby不会将内存释放回操作系统那么......我们总会有泄漏,对吗?

    例如:

    1. Ruby要求操作系统提供内存。
    2. OS为Ruby提供内存。
    3. Ruby释放内存,但GC仍未运行。
    4. Ruby要求操作系统提供更多内存。
    5. 操作系统为Ruby提供更多内存。
    6. Ruby运行GC,但为时已晚,因为Ruby已经问了两次。
    7. 依此类推。
    8. 我在这里缺少什么?

1 个答案:

答案 0 :(得分:1)

研究 GC 压缩和 (Un)frozen String Literals

“相同”字符串不一定相同

在 Ruby 2.7.0 之前,主线 Ruby 没有压缩垃圾收集。虽然我不完全了解所有内部结构,但要点是某些对象 couldn't be garbage collected。由于您使用的是 Ruby 2.3,因此在处理内存分配问题时需要牢记这一点。其他非 YARV 虚拟机可能会以不同的方式处理其内部结构,这就是为什么您在使用 JRuby 等替代引擎时可能会看到变化。

即使使用 Ruby 3.0.0-preview2,String literals aren't frozen by default,因此您当前的实现是每十分之一秒创建一个具有唯一对象 ID 的新 String 对象。考虑以下几点:

3.times.map { 'foo'.__id__ }
#=> [240, 260, 280]

尽管 String 对象看起来相同,但实际上 Ruby 将每个对象分配为内存中的唯一对象。因为循环迭代不是作用域门,所以 YARV 无法收集或压缩这些 String 对象。

默认启用冻结字符串

您可能还有其他问题,但您最大的问题似乎很可能是将所有这些字符串文字无限期地保留在无休止的 while 循环中。您可能可以通过使用 frozen String literals 来解决垃圾回收问题(不是内存泄漏)。考虑以下几点:

# run irb with universally-frozen string literals
RUBYOPT="--enable-frozen-string-literal" irb
3.times.map { 'foo'.__id__ }
#=> [240, 240, 240]

您也可以通过其他方式在代码中解决此问题,但减少保留在范围内的字符串文字的数量似乎是一个非常明智的起点。