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对象就是持有锁线程,我们就可以获取他的调研堆栈。

总结

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

相关推荐
拭心3 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王5 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡5 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道6 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库7 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道7 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe7 小时前
Android Hook - 动态加载so库
android
居居飒8 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He11 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗11 小时前
Android笔试面试题AI答之Android基础(1)
android