KOOM

KOOM

KOOM 原理

如何监控内存并 dump

定时轮询监控当前内存是否达到最大内存的阈值或者内存连续增加多少次;
为什么不使用主动 GC 的方式,GC 是 stop-the-world 会让进程卡死,频繁的 GC 会造成用户感知的明显卡顿,线上监控不需要太过精准。

如何 dump 内存堆栈

通过开启子进程的方式 dump 进程的 hprof,因为 Copy On Write 技术下父子进程共享同一块内存

Copy On Write 技术实现原理:
fork() 之后,kernel 把父进程中所有的内存页的权限都设为 read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU 硬件检测到内存页是 read-only 的,于是触发页异常中断(page-fault),陷入 kernel 的一个中断例程。中断例程中,kernel 就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。

难点一:主线程挂起问题

在 fork 子进程的时候 dump hprof。由于 dump 前会先 suspend 所有的 java 线程,等所有线程都挂起来了,才会进行真正的 dump。由于 copy-on-write 机制,子进程也会将父进程中的 threadList 也拷贝过来,但由于 threadList 中的 java 线程活动在父进程,子进程是无法挂起父进程中的线程的,然后就会一直处于等待中。
为了解决这个问题,Koom 和 Liko 采用欺骗的方式,在 fork 子进程之前,先将父进程中的 threadList 全部设置为 suspend 状态,然后 fork 子进程,子进程在 dump 的时候发现 threadList 都为挂起状态了,就立马开始 dump hprof,然后父进程在 fork 操作之后,立马 resume 恢复回 threadList 的状态 。

难点二:文件过大

由于 hprof 文件过大,需要对 hprof 文件进行裁剪。裁剪还有对用户数据脱敏的好处,只上传内存中类域对象的组织结构,并不上传真实的业务数据 (诸如字符串、byte 数组等含有具体数据的内容),保护了用户的隐私。

分析 hprof

暴力解析引用关系树的 CPU 和内存消耗都是很高的,即使在独立进程解析,很容易触发 OOM 或者被系统强杀,成功率非常低。因此需要对解析算法进行优化。
我们只关注泄漏对象和不合理使用的大对象:

分析工具的选择

LeakCanary 早期使用的解析引擎是 HAHA,LeakCanary 2.0 版本的发布,其研发团队推出了新一代 hporf 分析引擎 Shark。

解决混淆问题

Shark 支持混淆反解析,思路也很简单,解析 mapping.txt 文件,每次读取一行,只解析类和字段:

将混淆类名、字段名作为 key,原类名、原字段名作为 value 存入 map 集合,在分析出内存泄漏的引用路径类时,将类名和字段名都通过这个 map 集合去拿到原始类名和字段名即可,即完成混淆后的反解析 。

分析 hprof 总结

Ref

https://blog.csdn.net/qq_23191031/article/details/109457009
https://juejin.cn/post/6982121209144016910
https://juejin.cn/post/7018883931067908132