我的代码在 Ruby 2.4.4 上的 Sinatra 应用程序中泄漏内存,我可以在 irb 中重现它,尽管它并不完全稳定,我想知道其他人是否也有同样的问题。在正则表达式文字内插入大字符串时会发生这种情况:
class Leak
STR = "RANDOM|STUFF|HERE|UNTIL|YOU|GET|TIRED|OF|TYPING|AND|ARE|SATISFIED|THAT|IT|WILL|LEAK|ENOUGH|MEMORY|TO|NOTICE"*100
def test
100.times { /#{STR}/i }
end
end
t = Leak.new
t.test # If I run this a few times, it will start leaking about 5MB each time
现在,如果我GC.start
在此之后运行,它通常会清理大约最后的 5MB(或者它一直在使用的多少),然后t.test
只使用几 KB,然后几乎是 1 MB,然后是几 MB,然后回到每个 5MB时间,再一次,GC.start
只会收集最后 5 个。
在没有内存泄漏的情况下获得相同结果的另一种方法是替换/#{STR}/i
为RegExp.new(STR, true)
. 这对我来说似乎很好。
这是 Ruby 中的合法内存泄漏还是我做错了什么?
更新:
好的,也许我误读了这个。我正在查看 docker 容器在运行后的内存使用情况,GC.start
有时会下降,但由于 Ruby 并不总是释放它不使用的内存,我猜可能只是 Ruby使用了这个内存,然后,即使它没有被保留,它仍然没有将内存释放回操作系统。使用 MemoryProfiler gem,我看到 total_retained,即使在运行几次之后也是 0。
这里的根本问题是我们有容器崩溃,理论上是由于内存使用,但也许这不是内存泄漏,而只是缺少足够的内存来让 Ruby 使用它想要的东西?GC 是否有设置来帮助它在 Ruby 耗尽内存和崩溃之前决定何时清理?
更新2:这仍然没有意义 - 因为为什么Ruby会通过一遍又一遍地运行相同的进程来继续分配越来越多的内存(为什么它不使用之前分配的内存)?据我了解,GC 设计为在从操作系统分配更多内存之前至少运行一次,那么为什么当我多次运行时 Ruby 只是分配越来越多的内存呢?
更新 3:在我的独立测试中,Ruby 似乎确实接近了一个限制,无论我运行多少次测试(似乎通常在 120MB 左右),它都会停止分配额外的内存,但在我的生产代码中,我没有命中还存在这样的限制(它超过 500MB 而没有放慢速度——可能是因为在课堂上散布着更多这种内存使用的实例)。它会使用多少内存可能会有限制,但它似乎比运行此代码所需的预期高出许多倍(实际上一次运行只使用十几个 MB)
更新 4:我已将测试用例缩小到真正泄漏的范围内!从文件中读取多字节字符是重现真正问题的关键:
str = "String that doesn't fit into a single RVALUE, with a multibyte char:" + 160.chr(Encoding::UTF_8)
File.write('weirdstring.txt', str)
class Leak
PATTERN = File.read("weirdstring.txt").freeze
def test
10000.times { /#{PATTERN}/i }
end
end
t = Leak.new
loop do
print "Running... "
t.test
# If this doesn't work on your system, just comment these lines out and watch the memory usage of the process with top or something
mem = %x[echo 0 $(awk '/Private/ {print "+", $2}' /proc/`pidof ruby`/smaps) | bc].chomp.to_i
puts "process memory: #{mem}"
end
所以......这是一个真正的泄漏,对吧?