KOOM 小结

290 阅读2分钟

参考

【开源库剖析】KOOM V1.0.5 源码解析

流程

初始化流程起始于 CommonInitTask#init 方法,里面会调用 MonitorManager#addMonitorConfig()。这个方法采用泛型配置要初始化哪个 Monitor,很有意思。代码里用的是 OOMMonitorConfig,它继承此 MonitorConfig<OOMMonitor>,泛型是 OOMMonitor。所以 sdk 初始化的就是 OOMMonitor。

// addMonitorConfig 节选

val monitor = try {
  // 所有的 Monitor 用 kotlin 写的,都定义成 object。转换成 Java 后就是一个 INSTANCE 单例
  // 所以这里就反射读取 INSTANCE 字段
  monitorType.getDeclaredField("INSTANCE").get(null) as Monitor<M>
} catch (e: Throwable) {
  monitorType.newInstance() as Monitor<M>
}
MONITOR_MAP[monitorType] = monitor
monitor.init(commonConfig, config)

现在到了 OOMMonitor#init() 方法,该方法会调用 Application#registerProcessLifecycleObserver() 用于监听进程状态。在 ON_STATE 时会调用 startLoop(),该方法会调用 OOMMonitor 的父类 LoopMonitor#startLoop(),它会通过 handler post 一个 runnable,在该 runnable 对象中又调用 call() 方法,即 OOMMonitor#call(),再到 OOMMonitor#trackOOM() 方法

同时 runnable 对象是一个循环,会间隔一段时间再 post 一个自己,再次触发 trackOOM()。这就保证了能隔一段时间监控一次内存使用情况。

private fun trackOOM(): LoopState {
    // 该方法会计算当前内存使用情况
    SystemInfo.refresh()
    mTrackReasons.clear()
    // mOOMTrackers 是一个集合,里面有 HeapOOMTracker
    // 所以只看它的 track
    for (oomTracker in mOOMTrackers) {
        if (oomTracker.track()) {
            mTrackReasons.add(oomTracker.reason())
        }
    }
    // 只有 mTrackReasons 有值,才会去 dump
    if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {
        if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {
            MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")
        } else {
            // 开始 dump,见下面
            async {
                MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")
                dumpAndAnalysis()
            }
        }
        return LoopState.Terminate
    }
    return LoopState.Continue
}

下面是 HeapOOMTracker#track() :

// HeapOOMTracker#track()

override fun track(): Boolean {
    val heapRatio = SystemInfo.javaHeap.rate
    // 如果内存使用率达到一定阈值且比上次还增加到一定程度,就记一次
    if (heapRatio > monitorConfig.heapThreshold
        && heapRatio >= mLastHeapRatio - HEAP_RATIO_THRESHOLD_GAP
    ) {
        mOverThresholdCount++
    } else {
        reset()
    }
    mLastHeapRatio = heapRatio
    // 当次数超过一定限制时,就需要 dump 内存,分析是否有泄露
    return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
}

dump 过程

达到触发条件后会在子线程中 fork 新进程进行 dump,由子线程等待 dump 结束。dump 结束后启动一个位于新进程中的 service,由 service 执行分析及后续流程。

达到触发条件后由 OOMMonitor#trackOOM() 执行 dump 操作并进行分析。

// 异步
async {
    MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")
    dumpAndAnalysis()
}

dumpAndAnalysis() 会运行在子线程中,由它去等待 dump 结束,这样不会阻塞主线程

// dumpAndAnalysis 节选 

ForkJvmHeapDumper.getInstance().run {
    dump(hprofFile.absolutePath)
}
Thread.sleep(1000) // make sure file synced to disk.
startAnalysisService(hprofFile, jsonFile, mTrackReasons.joinToString())

startAnalysisService() 核心逻辑就是启动 HeapAnalysisService。它在清单中配置成了子进程,所以所有的分析流程都是在子进程中进行,不会影响主进程。关于分析的后续流程略。

<service
    android:name="com.kwai.koom.javaoom.monitor.analysis.HeapAnalysisService"
    android:process=":heap_analysis" />

现在回到 ForkJvmHeapDumper#dump() 方法,它是整个 dump 的核心

// 先暂停主进程中的所有线程,然后 fork
int pid = suspendAndFork();
if (pid == 0) {
  // Child process
  Debug.dumpHprofData(path);
  exitProcess();
} else if (pid > 0) {
  // Parent process
  // 当前线程会等待 pid 结束,也就是子进程
  // 由于整个 dump 过程是在子线程中是进行的,所以不会卡住当前应用
  dumpRes = resumeAndWait(pid);
}