如何追踪 rails 应用程序中的内存泄漏?

How can I track down a memory leak in a rails app?

我的 Rails 应用程序中有一个 class 可以评估应用程序在业务流程中的位置。为此,我有一个任务模型的概念,它使用单个 table 继承来提供多态性。 Task 的每个子class 可以通过执行自己的数据库查询来确定它是否完成,通常通过多态关联在关联模型上。

每次此求值器 运行 都会消耗更多内存,即使在手动调用 GC.start 后也不会释放内存。使用 ObjectSpace.object_counts 我制作了一个图表,显示了随着 TOTAL 计数增长的最密切相关的对象类型。 T_IMEMO 增长最快,T_STRING 位居第二。

内存分析器告诉我大部分内存都分配给了 ActiveRecord。但是在迭代之间,我没有保留新的 ActiveRecord 对象。代码大致是:

CustomerApplication.all.in_batches(of: 10).each do |batch|
  batch.each do |customer_application|
    Evaluator.new(customer_application).evaluate!
    GC.start
    # collect statistics
  end
end

据我所知,我没有使用任何可以在迭代之间存储数据的 class 属性或全局变量。

我还尝试在每次迭代时禁用 ActiveRecord 日志记录并清除 ActiveRecord 缓存。

Evaluator 代码的核心如下所示:

class Evaluator
  # ...

  def stage(name, &block)
    stage = Stage.new(@customer_application)
    stage.instance_eval(&block) if block_given?
    @stages << stage
  end

  def evaluate!
    @stages = []

    stage :first_stage do
      step :first_step do
        task(Task::SpecificTask)
        phantom_task(Task::ReportTask, reference: ..., key: 'report_a')
      end
    end

    stage :second_stage do
      step :first_step do
        task(Task::ReportTask, reference: ..., key: 'report_a')
      end

      step :second_step do
        task(Task::ReportTask, reference: ..., key: 'report_b')
      end
    end

    # A ton more stages/steps/tasks

    Task.transaction do
      self.tasks.each do |task|
        task.enabled = true
        task.evaluate_completion!
        task.evaluate_optional!

        task.save!
      end

      # Disable all other tasks
    end
  end
end

class Stage
  # ...

  private

  def step(name, reference: nil, deps: [], &block)
    step = Step.new(name, @customer_application, reference, deps)
    step.instance_eval(&block) if block_given?
    @steps << step
    step
  end
end

class Step
  # ...

  private

  def task(klass, reference: nil, key: nil)
    existing_task = @customer_application.tasks.find_by(type: klass.name, reference: reference, key: key)

    task = (existing_task || klass.new(customer_application: @customer_application, reference: reference, key: key))

    @tasks << task

    task
  end

  def phantom_task(klass, reference: nil, key: nil)
    readonly_klass =
        Class.new(klass) do
          def self.name
            superclass.name
          end

          def readonly?
            true
          end

          def optional?
            true
          end

          def phantom_task?
            true
          end
        end

    task = readonly_klass.new(customer_application: @customer_application, reference: reference, key: key)

    @tasks << task

    task
  end
end

我设法弄明白了。我在每次迭代中实例化运行时 类,这显然没有得到 GC。重构为不使用 Class.new 解决了问题。

所以如果有人用谷歌搜索这个,Class.new 会造成内存泄漏。