GC/JIT 抑制
概述
所谓 Android GC/JIT 抑制
,即是将 Android GC(部分)/JIT Task 处理任务的过程(Run 方法) hook,强行 sleep 一段时间。因为 JIT 和 GC 目前均为单线程执行,所以没有并发问题,也刚好阻塞后续任务。
github 非官方实现(提供思路,但有一些问题): https://github.com/RicardoJiang/android-performance/blob/main/startup-optimize/src/main/cpp/StartUpOptimize.cpp
抑制目的
减少启动阶段系统的 CPU 使用,预期有一些冷启动收益。
实现原理
- 复杂版
按照分享中所讲,需要找到虚函数地址,然后替换函数指针调用 - 简单版
直接使用 inlinehook,其实原理一致
基础理论
- 假如抑制 gc 时间过长,会不会启动阶段造成 OOM?
不会
抖音分享中提到,Explicit GC
和Alloc GC
,其实也还有其他的种类。我们 hook 的是 ConcurrentGC 这一种
所以,当内存达到 GC 阈值的时候,任何 Alloc 动作都会触发 GC,block 的是当前线程,所以不会 OOM,但是并不一定是个好事情,主线程同样也需要等待 - 如何查看一个函数的符号?
nm 等各种命令 - 如何 hook 一个 native 函数?什么时候可以 PLT?什么时候不可以?为什么有这么多的 xxx_dlfcn 的实现?
一时半会说不清楚,知道 xdl 处理了什么问题 看懂 xhook/bhook
GC 抑制实现
先看一下 github 版本的实现,其实现和抖音分享中所说的步骤一致,通过虚函数表找到 ConcurrentGCTask 的 Run 函数,然后替换函数
上述代码问题
- libart.so 的路径问题
其实 libart.so 各个版本不一致,这种路径为低版本情况,高版本直接 crash,handle 也没有判空。那么我们难道要分版本获取 libart.so? - 原方法调用不对
这个地方一开始没注意看,引我进入了误区,以为是版本适配问题,浪费了过多时间。
hookRun 中使用了 originFun(thread),originFun 类型为(void()(void)) 单参数函数指针,这样调用理论上应该必定出问题,因为 Run 方法为 Task 类的成员函数,调用时要传入对象实例,应该为双参数 originFun(task, thread)。但是实际情况是有的版本出问题,有的版本不出问题,具体原理没追究。 - ConcurrentGCTask 和 ConcurrentGCTask.Run 符号问题
Android 5.x 版本不存在 ConcurrentGCTask。符号的问题,我们最好是各个版本源码 和 libart.so nm 看一下,libart.so 可以通过模拟器去拿到 - 库选择
enhanced_dlfcn 这个可以使用,但是 libart 的路径问题,可能需要自己处理一下,比如 /apex/xxxx 版本也有不同。
查找虚函数表和最后 replace 的过程,可以看出作者调研了很多。
但是其实,我们知道函数符号的情况下,可以直接 inlinehook 任意方法,省去各种函数查找替换的过程。
当然 inlinehook 可能稳定性差了一些,且不支持 armeabi
(但是目前 armeabi 量级非常稀少),但是 shadowhook 应该也是广泛使用的一种 inlinehook 库,作者也是 xcrash/xdl/xhook/bhook 等等的作者,nativebitmap 也已经使用了类似功能,可以线上实验关注一下稳定性,如果有稳定性问题,那就再还原逻辑。
最终使用方案
使用 inline hook 分版本 hook 不同函数,省去 libart 路径处理,查找函数和替换的处理
存储 originGC,即是原函数指针,在 hook 方法体中调用即可
注意 func 类型,双参数不是单参数,是类成员方法
JIT 抑制实现
同 GC 抑制,只是 hook 的函数符号不同罢了。
至于抖音提到的 global reference 越界的问题
,不知道在表述什么问题。
如果是说 global reference overflow 的话,感觉很怪。
因为 global reference 的限制很高,一般 51200
且 JitCompileTask 析构函数中会删掉 global ref,不知道为何要费劲心力的把这行代码抹掉,可能是看了源码的注释?想让低版本和高版本一样被优化?
感觉没有必要,如果强行想实现的话,对于 inlinehook 没有什么办法,只能 hook 构造函数,在调用构造函数以后,自己手动调用 DeleteClobalRef kclass=null 了
源码
GC 文章: GC源码分析
本文示例代码:GC/JIT Delay