Ruby内存泄漏(MRI)

Sas*_*ash 8 ruby memory-leaks

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

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

while true
   #do something
   sleep 0.1
end
Run Code Online (Sandbox Code Playgroud)

例如,我可以写入数组然后在循环中清理它,或者只发送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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

使用JRuby时,相同的代码运行完美,没有任何泄漏.

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

来自Joe Edgar的堆栈溢出:

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

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

例如:

  1. Ruby从操作系统请求内存.
  2. OS为Ruby提供内存.
  3. Ruby释放了内存但GC仍未运行.
  4. Ruby要求OS提供更多内存.
  5. 操作系统为Ruby提供更多内存.
  6. Ruby运行GC但是已经太晚了,因为Ruby已经问了两次.
  7. 等等.

我在这里错过了什么?

Tod*_*obs 1

研究 GC 压缩和(未)冻结的字符串文字

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

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

即使使用 Ruby 3.0.0-preview2,默认情况下字符串文字也不会冻结,因此您当前的实现是每隔十分之一秒创建一个具有唯一对象 ID 的新 String 对象。考虑以下:

3.times.map { 'foo'.__id__ }
#=> [240, 260, 280]
Run Code Online (Sandbox Code Playgroud)

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

默认启用冻结字符串文字

您可能还有其他问题,但您最大的问题可能是在无限的 while 循环中无限期地将所有这些字符串文字保持在范围内。您也许可以通过使用冻结的字符串文字来解决垃圾收集问题(这不是内存泄漏)。考虑以下:

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

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