在第二部分继续之前,先为错过上篇的朋友做一个两句话速览:在 Android 16 上,当你把 Java 调试器(JDWP,即 Java 调试线协议)接入一个大型 debuggable 应用的那一刻,整个进程会直接卡死并抛出 ANR。罪魁祸首是垃圾回收器线程与 ART 原生调试层之间的用户态锁顺序(ABBA)死锁,该问题已进入公开追踪系统,编号 Issue #530992434,已在两款设备上复现,并通过二分法定位到了一次具体的 ART 主线更新。
好消息是,这个 bug 已经被 ART 团队确认并排入了待处理列表。坏消息是,主线修复不会“明天”就到来。它还需要通过评审,赶上 com.android.art 的更新列车,再通过 Google Play 系统更新推送到设备。而你希望调试自己的应用不是“某一天”,而是现在。
![]()
在上篇中我提过几种变通方案:固定到较旧的 ART 主线版本、把调试拆成轻量的模块、抓取原生转储来替代 Java 转储。这些方法管用,但都很沉重——要么改变你的开发环境,要么始终无法给你最想要的那一样东西:舒舒服服地在单体应用里直接调试。
所以这里提供另一个选项——而且我们真心更喜欢这一个。一个微小的原生钩子,直接在 ART 内部,在 JDWP 事件洪流刚露头时就将其扼杀在摇篮里,连触发死锁的机会都不给。应用依然保持大型且可调试,但现在接入调试器时就像什么事都没发生过。整套方案已经打包成了一个现成的库——JdwpSilencer,但接下来我会把它拆开讲清楚,这样你也可以从源码构建同样的东西,完全不必依赖任何外部引入。
先做一句免责声明:这并非万能银弹。这个钩子让很多人的调试功能恢复了正常,但对于一部分开发者——那些在旧版本 ART 上一切正常的开发者——调试器在钩子之后反而卡在了另一种状态:第一次碰到断点就会挂起。好消息是,针对这种情况现在也有了一个终止开关,会在后面的单独小节中说明。
我们先回顾一下到底是什么触发了死锁。需要三种信号风暴在同一时刻冲击调试层,恰好发生在调试器接入的那一刻:CLASS_PREPARE,几万个类同时加载;THREAD_END / THREAD_START,成百上千个短生命周期线程的诞生与消亡;GC 压力,启动阶段大量的内存分配。让这三个信号在接入窗口内重叠,运行时就会掉进 ObjectTagTable、commonRef 以及系统弱引用处理之间的那条窄缝里。
还有另一个更落地的一面:JDWP 是一个同步协议,每触发一个这样的事件,运行时都得等待调试器一侧的回复。事件量一爆炸,等待链条一缠结,死锁便几乎不可避免。而我们所做的,就是在事件被投递之前,用一个精确定位的钩子让这股浪潮停下来。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.