主线程锁监控原理和实现

305 篇文章 11 订阅
59 篇文章 3 订阅

去年处理一些性能问题的时候,遇到过一些主线程等待锁的问题,如果主线程等待锁的时间太长,就会出现主线程卡顿甚至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 这个库去调用。这个库的关键实现在 https://github.com/Rprop/ndk_dlopen/blob/master/dlopen.c 里,他会插入一段汇编代码,模拟系统调用去调用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字段后,代码如下:

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

图片

总结

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

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

根据提供的引用内容,我们可以使用Python中的`threading`模块来实现Selenium子线程监控线程浏览器。具体步骤如下: 1. 首先,我们需要导入`threading`模块和`selenium`模块。 2. 接着,我们需要定义一个函数,用于在子线程中监控线程浏览器。在该函数中,我们可以使用`selenium`模块中的`WebDriverWait`方法来等待线程浏览器的某个元素,如果该元素在规定时间内没有出现,则说明线程浏览器已经关闭。 3. 然后,我们需要在线程中启动子线程,并将子线程设置为守护线程。这样,当线程结束时,子线程也会随之结束。 下面是一个示例代码,演示了如何使用Python中的`threading`模块来实现Selenium子线程监控线程浏览器: ```python import threading from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 定义子线程函数 def monitor_browser(driver): # 等待线程浏览器的某个元素 WebDriverWait(driver, 3600).until(EC.presence_of_element_located((By.ID, "element_id"))) print("Main thread browser closed.") # 启动线程 driver = webdriver.Chrome() driver.get("https://www.example.com") # 启动子线程 t = threading.Thread(target=monitor_browser, args=(driver,)) t.setDaemon(True) t.start() # 关闭线程浏览器 driver.quit() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值