目录

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 使用,预期有一些冷启动收益。

实现原理

  1. 复杂版
    按照分享中所讲,需要找到虚函数地址,然后替换函数指针调用
  2. 简单版
    直接使用 inlinehook,其实原理一致

基础理论

  1. 假如抑制 gc 时间过长,会不会启动阶段造成 OOM?
    不会
    抖音分享中提到,Explicit GCAlloc GC,其实也还有其他的种类。我们 hook 的是 ConcurrentGC 这一种
    所以,当内存达到 GC 阈值的时候,任何 Alloc 动作都会触发 GC,block 的是当前线程,所以不会 OOM,但是并不一定是个好事情,主线程同样也需要等待
  2. 如何查看一个函数的符号?
    nm 等各种命令
  3. 如何 hook 一个 native 函数?什么时候可以 PLT?什么时候不可以?为什么有这么多的 xxx_dlfcn 的实现?
    一时半会说不清楚,知道 xdl 处理了什么问题 看懂 xhook/bhook

GC 抑制实现

先看一下 github 版本的实现,其实现和抖音分享中所说的步骤一致,通过虚函数表找到 ConcurrentGCTask 的 Run 函数,然后替换函数

上述代码问题

  1. libart.so 的路径问题
    其实 libart.so 各个版本不一致,这种路径为低版本情况,高版本直接 crash,handle 也没有判空。那么我们难道要分版本获取 libart.so?
  2. 原方法调用不对
    这个地方一开始没注意看,引我进入了误区,以为是版本适配问题,浪费了过多时间。
    hookRun 中使用了 originFun(thread),originFun 类型为(void()(void)) 单参数函数指针,这样调用理论上应该必定出问题,因为 Run 方法为 Task 类的成员函数,调用时要传入对象实例,应该为双参数 originFun(task, thread)。但是实际情况是有的版本出问题,有的版本不出问题,具体原理没追究。
  3. ConcurrentGCTask 和 ConcurrentGCTask.Run 符号问题
    Android 5.x 版本不存在 ConcurrentGCTask。符号的问题,我们最好是各个版本源码 和 libart.so nm 看一下,libart.so 可以通过模拟器去拿到
  4. 库选择
    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