我的每个 rails 项目,都发现有长期运行或内存逐渐增加、只增不减的问题。本着严于律己、宽于待人的原则,我一直以为是自己代码写得太烂,出现了内存泄露。
直到今天做了一个实验,我怀疑这个问题和 ruby 本身有关……
环境是 ruby3.0.1 + rails6.1.4。
实验 1:
100.times do
long_str = "a" * 10_000_000
end
render json: 'OK'
代码简单至极,就是创建一个长字符串,运行 100 次只是为了效果更明显。
这段代码的效果是:每次运行大约增加 1g 内存,且不会释放。
按理说,render 之后整个请求已经结束,long_str 已经没有继续使用的可能,又不是什么实例变量、全局变量,ruby 应该把 long_str 垃圾回收,但是并没有。
我曾经以为 long_str 已经被回收,只是 ruby 占着内存的坑不想还给系统而已,除非系统内存不足才会还。但是我冒着死机的风险运行到系统内存全部吃光,ruby 进程的内存并没有分毫减少。
实验 2:
long_str = "a" * 10_000_000
long_str = nil
如果之前是因为 ruby 不知道 long_str 已经没用了所以没回收,那现在用 long_str = nil 告诉 ruby 总可以了吧?
结果:和实验 1 没有什么区别。
实验 3:
long_str = "a" * 10_000_000
long_str = nil
GC.start
现在每一轮循环结束后手动执行垃圾回收。确实有效果,ruby 不会内存增加 1g 了,但是每次会增长 10m,而且永远不会释放。
我就想知道,这个问题到底怎么解决,到底怎么写才不会“内存泄露?即使手动执行垃圾回收(在我看来这些代码是多余的),ruby 内存仍然会永久性增长?
代码已经简单到极点了,如果连这么简单的代码都有内存问题,那真正的项目中怎么可能不出现所谓“内存泄露”?