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

总结

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

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui