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

总结

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

相关推荐
Dnelic-37 分钟前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen3 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年10 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿13 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神14 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛14 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法15 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter16 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快17 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl17 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5