Ruby(MRI)服务 GC 时间上升、内存不回落,Puma worker 被重启或 OOMKilled。
接口 p99 周期性抖动,日志或监控出现 GC 停顿;GC.stat[:time] 快速增长,major_gc_count 频繁增加,worker RSS 居高不下。Puma 可能因内存超限被重启,容器出现 oom_kill。
先看进程和 cgroup 内存,再看 GC 统计:
ps -o pid,rss,etime,cmd -C ruby
grep -E 'VmRSS|VmSwap' /proc/<PID>/status
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.events
在应用内或 rails runner 中读取统计:
bundle exec rails runner 'p GC.stat.slice(:count, :major_gc_count, :minor_gc_count, :time, :heap_live_slots, :heap_free_slots, :heap_allocated_pages, :old_objects)'
对运行中的进程可用 rbtrace 读取:
gem install rbtrace
rbtrace -p <PID> -e 'GC.stat.each { |k, v| puts "#{k}=#{v}" }'
计算 GC 时间占比:间隔 60 秒取两次 GC.stat[:time],用增量除以 60000 ms;同时观察每分钟 major GC 次数。定位分配来源:
bundle exec ruby -r memory_profiler -e 'report = MemoryProfiler.report { run_workload }; report.pretty_print(to_file: "/tmp/mem.txt")'
易误判:RSS 不下降不等于 Ruby 对象泄漏 —— MRI 会保留堆页,释放的对象槽位会被复用但页面不一定还给 OS。应看 major GC 后的 heap_live_slots 是否持续增长,以及 RUBY_GC_HEAP_GROWTH_FACTOR、malloc arena 和 native 扩展的影响。
MALLOC_ARENA_MAX。preload_app! 后 CoW 页面被写脏,每个 worker 的私有内存持续增长。GC.disable/GC.stress,导致回收行为异常。memory_profiler 找到分配热点,优先减少请求路径上的大对象和重复分配。RUBY_GC_HEAP_GROWTH_FACTOR(如 1.5)、RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR、RUBY_GC_MALLOC_LIMIT,用相同流量对比 GC 时间和 RSS。MALLOC_ARENA_MAX=2,或使用 jemalloc;观察 RSS 与 GC 停顿变化。GC.start/GC.compact(或用等价封装)降低 CoW 破坏。GC.compact 的改进;升级后用同一压测回归。GC.stat[:time] 占比、major GC 频率、worker RSS 和 memory.events。heap_live_slots 稳定,说明什么?preload_app! 后 worker 内存仍会增长?GC.stat[:time] 增量计算占墙钟时间的比例,并关联 p99 抖动与 major GC 频率;占比高且与延迟相关才算瓶颈。