如何排查Rails中大量内存未被垃圾回收的问题?
Rails 6.1.7/Ruby 3.1.2 内存泄漏排查方案
问题背景
我们维护基于Ruby 3.1.2/Rails 6.1.7、Postgres的应用,此前因内部工具执行无分页ActiveRecord查询,导致部分实例内存飙升数十MB且数天无法释放。添加分页后大幅增长问题解决,但小负载场景下仍存在内存渐进式增长,需区分是内存碎片还是真正的内存泄漏,同时希望能查看运行中实例的内存对象图。
查看运行中实例内存对象图的方法
1. ObjectSpace + ruby-debug 实时分析
- 在Rails控制台(生产环境操作需谨慎,避免长时间占用资源)中,用
ObjectSpace遍历内存对象:- 统计特定类实例数量:
ObjectSpace.each_object(ActiveRecord::Base).count - 查看对象引用链:对可疑对象执行
debug(obj)(需安装ruby-debug),分析其引用关系,判断是否存在意外的持久化引用。
- 统计特定类实例数量:
- 注意:
ObjectSpace会带来性能开销,使用后立即退出控制台。
2. derailed_benchmarks 深度内存分析
- 安装
derailed_benchmarks(建议仅在development组添加),针对Rails应用提供内存泄漏检测:- 查看Gem内存占用:
bundle exec derailed bundle:mem - 模拟请求跟踪内存变化:
bundle exec derailed perf:mem,对比多次请求后的对象留存情况,生成详细的对象分配报告。
- 查看Gem内存占用:
3. heap_dump 生成内存快照
- 安装
heap_dumpgem,在应用中触发快照生成:- 通过控制器动作或rake任务添加触发逻辑:
HeapDump.dump('/tmp/heap_snapshot.json') - 对比不同时间点的快照,找出持续增长的对象集合,定位内存泄漏点。
- 通过控制器动作或rake任务添加触发逻辑:
- 生产环境建议将快照输出到临时目录,避免占用过多磁盘空间。
区分内存碎片与内存泄漏的方法
- 手动触发GC验证:在控制台执行
GC.start; GC.start(连续两次确保完全回收),用get_process_mem查看内存:GetProcessMem.new.mb。若内存仍远高于基线,说明存在未回收对象;若回落但未到初始值,大概率是内存碎片。 - 对比Web请求与控制台的内存行为:控制台执行查询后关闭即释放内存,但Web请求后不释放的话,可在请求末尾添加
GC.start,观察内存变化:- 若内存回落,说明是GC时机问题;若不回落,说明对象被全局变量、单例对象或后台线程持有引用。
- 检查后台线程与全局缓存:Rails 6.x默认
memory_store为进程独立缓存,检查Rails.cache.stats查看缓存键数量,确认是否有未清理的大缓存项;同时排查后台线程是否持有请求对象的引用。
ActiveRecord相关排查点
- 检查数据库连接状态:执行
ActiveRecord::Base.connection_pool.stat,若存在未释放的连接,可能关联着结果集对象导致内存无法回收。 - 排查关联预加载的隐藏引用:即使主结果集被销毁,预加载的关联对象若被全局观察者、回调等持久化引用,也会导致内存泄漏。可通过
ObjectSpace.each_object(YourModel).select { |obj| obj.association(:related_model).loaded? }查看未回收的关联对象。
内容的提问来源于stack exchange,提问作者JakeRobb
相关产品推荐
相关产品推荐

