Android主线程锁监控原理和实现

去年处理一些性能问题的时候,遇到过一些主线程等待锁的问题,如果主线程等待锁的时间太长,就会出现主线程卡顿甚至ANR。所以我们需要通过技术手段去检测可能存在的锁等待。

技术基础

如何检测主线程等待锁?这里涉及一些技术基础知识点:

线程状态

Java线程可以通过 getState获取线程的状态,线程状态包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITTING、TERMINATED 几个状态,其中 BLOCKED 就是等待锁的时候进入的阻塞状态。

获取线程想要竞争的Monitor

在art源码的 monitor.cc 里面通过 GetContendedMonitor 函数可以获取某个 Thread 在等待的 Monitor:

获取Monitor被持有的线程

也是在 monitor.cc 里面可以通过 GetLockOwnerThreadId 获取 monitor 被持有的线程:

实现思路

有了上述技术基础,我们就可以出现一个大致的主线程锁等待的监控。我们可以按一定频次轮训主线程的状态,如果主线程处于blocked状态,那么说明主线程和其他线程在竞争锁。通过上述的2个jni函数,就可以得到被等待的线程的id。

具体实现和问题

在具体实现的时候我们需要处理一些问题。这部分我们来一一介绍。

调用jni函数

第一个问题是如何调用 Monitor::GetContendedMonitor 和 Monitor::GetLockOwnerThreadId。 在c/c++中,我们可以通过 dlopen打开一个so文件,通过dlsym获取函数的地址。但是dlopen和dlsym在Android中被限制调用。我们可以使用 ndk_dlopen 这个库去调用。这个库的关键实现在 github.com/Rprop/ndk_d... 里,他会插入一段汇编代码,模拟系统调用去调用dlopen和dlsym。 这块其实我也不是很明白,只能从issus里找到一个大概的原理解释。不过没关系,他的代码很少,我们直接copy使用即可。 这里还有个问题就是GetContendedMonitor函数的参数是一个jni的Thread指针。我们如何传入Thread指针呢?索性Java的Thread对象里面保存了jni层的对象指针地址,我们通过反射就可以获取Thread指针。 获取到函数签名之后,通过ndk_dlopen调用即可获取线程等待的锁的持有线程的id:

对齐线程id

上面的代码我们获取了持有锁的线程的id,但是实际上这个id和java层的线程id并不是同一个线程id。 jni层获取的线程id调用了 Monitor::GetOwnerThreadId: 这里取的是tls32_里的thin_lock_thread_id: 而Jave层的线程id则是Java层自己维护的: 创建线程的时候会调用 nextThreadID 复制给 tid,这只是一个全局的数量自增一下。 那么我们怎么通过jni返回的线程id匹配到java的Thread对象呢?我们可以从c层的Thread对象的对象布局入手。通过分析Thread类的代码,去掉函数、去掉static字段后,代码如下:

c 复制代码
class EXPORT Thread {
  struct alignas(4) tls_32bit_sized_values {
    Atomic<uint32_t> state_and_flags;
    uint32_t thin_lock_thread_id;
    uint32_t tid;
    const bool32_t daemon;
    bool32_t throwing_OutOfMemoryError;
    //...
  } tls32_;
}

这里刚好 tls32_ 是 Thread 对象第一个定义的字段,所以tls32_就是Thread指针指向内存的开头,而 tls32_里面定义的对象顺序是 state_and_flags、thin_lock_thread_id、tid...., 所以Thread指针指向内存再+2,就是指向的 thin_lock_thread_id。 我们传入Java Thread 的 nativePeer,再加上2,就是thin_lock_thread_id 的地址,所以我们可以通过Java Thread算出他的 thin_lock_thread_id,和返回的持有锁线程id做对比,如果对比上了,这个Java Thread对象就是持有锁线程,我们就可以获取他的调研堆栈。

总结

到这里我们基本把主线程锁等待监控的方案思路和关键技术点过了一遍,通过上述方法,我们就能实现我们自己的主线程锁监控。

相关推荐
北漂Zachary6 小时前
四大编程语言终极对比
android·java·php·laravel
学习使我健康10 小时前
Android App 启动原理
android·android studio
TechMix11 小时前
【性能工具】atrace、systrace、perfetto抓取的trace文件有何不同?
android·性能优化
张小潇11 小时前
AOSP15 WMS/AMS系统开发 - 窗口层级源码分析
android·前端
努力努力再努力wz14 小时前
【MySQL入门系列】掌握表数据的 CRUD:DML 核心语法与执行逻辑解析
android·开发语言·数据结构·数据库·c++·b树·mysql
zh_xuan16 小时前
Android gradle任务
android·gradle构建
Grackers17 小时前
Android Perfetto 系列 10:Binder 调度与锁竞争
android·binder
李白你好17 小时前
Android 自动化渗透测试指令生成
android·自动化
CeshirenTester18 小时前
Claude Code 不只是会写代码:这 10 个 Skills,才是效率分水岭
android·开发语言·kotlin
朝星19 小时前
Android开发[2]:Flow
android·kotlin