参考
流程
初始化流程起始于 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);
}