三方库总目录: 三方库
KOOM 解析
KOOM 结构分块比较清晰,有几个明显的模块。
一个比较合理的三方库拥有的模块,这里不仅仅会统计信息,还会有解析和一个比较人性化的输出供用户使用,那么之后自己做类似的东西的时候,如果要推广那就不仅仅只能停留在功能实现上,还要有完整的输出和人性化的使用方式,哈哈,这个库就像再说,dump 信息你不会我来做, 分析内存 你不会 我也帮你做,最后还输出一个 json 文件给你看。
类似的,还有 google systrace 给 dump 出来信息之后还会输出为 html 供查看,这个不是更六了。
我的模块划分比较粗暴和简单就是根据 KOOM 的库的 module 去划分的,实际上这种不太合理,这个是 KOOM 的想法,看起来有些割裂,最好的还是按照那个套路,从用户角度切入然后一步步的去研究。
| 模块 | 说明 |
|---|---|
| Core | 这里就是 KOOM 的核心抽象类集和的地方,主要就是 monitor base 等等 |
| native leak | native 的 泄露 HOOK 和 分析模块 |
| thread leak | 线程的泄露相关 |
| java leak | Java JVM 的泄露相关 |
| koom-common | 这里主要是 KOOM 依赖的三方库,例如 shark 、xhook 等 |
参考了掘金大佬的文章,发现 KOOM 真的是挺好的,模块分的清晰,又引入了一些新点子,不仅仅是沿袭以前别的框架的路子,也开辟了一个别的模式,例如通过遍历而不是通过 GC 去侦测泄露(因为线上体验的问题),使用子进程去 dump 信息来完成 hprof 的收集和分析,子进程的运行不会影响父进程,不会引起父进程的阻塞等等。
这里也包含一个信息就是在 linux 系统中,子进程从父进程 fork 出来之后会拷贝和保留父进程的所有信息,所以这时候在子进程 dump hprof 文件,那么此刻也可以看成是父进程的信息,这个是 linux 的写时复制的特点,如果不知道系统的这个设计又怎么能想出来在子进程里面去 dump 和 分析这种好点子呢?说明人家不仅聪明,还知识渊博,我能看出来,也证明这块我也补充了一些,哈哈,没有那么单薄了。
使用流程
Demo 中的用法:
object CommonInitTask : InitTask {
override fun init(application: Application) {
// builder 模式构建参数
val config = CommonConfig.Builder()
.setApplication(application) // Set application
.setVersionNameInvoker { "1.0.0" } // Set version name, java leak feature use it
.build()
// 通过一个 manager 去初始化核心逻辑,使用 apply 完成链式调用,记住 apply 传入的是 this 也就是 manager 就是调用的他的方法
MonitorManager.initCommonConfig(config)
.apply { onApplicationCreate() }
}
}
重要的方法有:
@JvmStatic
fun onApplicationCreate() {
// 这里是初始化 app 相关的,主要就是对生命周期的监听,这样去对 activity 的状态获得记录,另外还有对 ProcessLifecycleOwner(AndroidX 的) 的 observe 去监听是否在前台。
registerApplicationExtension()
// 这里是使用 ProcessLifecycleOwner 侦测到 onStateChange 之后去 MONITOR_MAP 的搞一搞,而这个 MONITOR_MAP 是哪里来的呢,是 addMonitorConfig 这里添加的
registerMonitorEventObserver()
}
/**
* 这里就是核心了,这里就是加上了对应的 monitor 去初始化,使用到那个就去初始化
*/
// 根据不同的种类去初始化
@JvmStatic
fun <M : MonitorConfig<*>> addMonitorConfig(config: M) = apply {
var supperType: Type? = config.javaClass.genericSuperclass
while (supperType is Class<*>) {
supperType = supperType.genericSuperclass
}
if (supperType !is ParameterizedType) {
throw java.lang.RuntimeException("config must be parameterized")
}
val monitorType = supperType.actualTypeArguments[0] as Class<Monitor<M>>
// 存在则返回
if (MONITOR_MAP.containsKey(monitorType)) {
return@apply
}
// 反射获取 monitor ,为什么使用反射呢,因为反射目前的效率已经特别高了,也可以少些挺多的模板代码的,不用特别忌讳反射,如果能提效,那就用,技术无长短。
val monitor = try {
monitorType.getDeclaredField("INSTANCE").get(null) as Monitor<M>
} catch (e: Throwable) {
monitorType.newInstance() as Monitor<M>
}
// 这里去设置为对应的 monitor 占坑
MONITOR_MAP[monitorType] = monitor
// 对 monitor 进行 init 初始化
monitor.init(commonConfig, config)
monitor.logMonitorEvent()
}
代码分析
代码核心就是 monitor base 相关类,其中 Monitor 类做了对对应 config 的保存以及一个 flag 的设置去证明已经初始化了,直接继承 Monitor 的不太多一个是 OOMTracker 一个是 LoopMonitor,其中 OOMTracker 主要就是为了监控 OOM,而 LoopMonitor 则是其他功能的很多基类。
OOMTracker 的主要是会导致 OOM 的几种类别,通过这个去模板了几个方法让实际的去统筹 OOM 的几种触发机制,具体的就是 FastHugeObject 、FdOOM、HeadOOM、PhysicalMemory、ThreadOOM 等。
LoopMonitor 则是几个侦测泄露的方式。典型的如,OOMMonitor、LeakMonitor、ThreadMonitor,针对不同的 Monitor 则有其内部更加具体的子类或者组件去实现他们的功能。
// OOMMonitor 核心分析
override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) {
throwIfNotInitialized { return }
if (!isMainProcess()) {
return
}
MonitorLog.i(TAG, "startLoop()")
if (mIsLoopStarted) {
return
}
mIsLoopStarted = true
// 这里在父类里面会先去 post 一个 task 再达到 delay 时间的时候去 remove 对应的 task
super.startLoop(clearQueue, postAtFront, delayMillis)
// 这里 post 的 task 如果没有被父类的清除掉那么我们就可以认为发生 ANR 了,但是 5000ms 还是有点长,哈哈
getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)
}
Leak 相关的则是使用的 native 去分析。