当 ART 工厂收到 "紧急报告" 信号:一个关于 Android Trace 文件的故事

59 阅读6分钟

工厂背景:ART 虚拟机的日常运作

想象 Android 系统是一个大型工厂,每个应用都是工厂里的独立车间,而 ART 虚拟机就是管理这些车间运作的智能控制系统。车间里有很多工人(线程)在忙碌,有的处理用户点击(主线程),有的搬运数据(Binder 线程),还有的清理垃圾(GC 线程)。

这个工厂有个特殊规定:当车间出现异常(比如工人长时间不响应),可以发送一个 "紧急报告" 信号(SIGQUIT,编号 3),让管理层生成一份详细的工作状态报告,这就是我们说的 Trace 文件。

第一章:门卫 SignalCatcher 的紧急响应

在 ART 工厂门口,有个叫 SignalCatcher 的门卫(位于 signal_catcher.cc),他的主要工作是监听外部信号:

cpp

运行

// SignalCatcher.cc: 门卫的工作流程
void* SignalCatcher::Run(void* arg) {
  // 初始化工作环境
  SignalSet signals;
  signals.Add(SIGQUIT); // 关注紧急报告信号
  signals.Add(SIGUSR1); // 其他辅助信号
  
  while (true) {
    int signal_number = WaitForSignal(...); // 监听信号
    switch (signal_number) {
      case SIGQUIT:
        HandleSigQuit(); // 收到紧急报告,开始处理
        break;
      // 其他信号处理...
    }
  }
}

某天,管理员通过命令 "adb shell kill -3 888" 向车间 888 发送了紧急报告信号,门卫 SignalCatcher 立刻捕获到这个信号,开始执行紧急响应流程。

第二章:厂长 Runtime 的全局调度

门卫通知了工厂总经理 Runtime(位于 runtime.cc),总经理立即启动全厂状态收集流程:

cpp

运行

// runtime.cc: 总经理的调度指令
void Runtime::DumpForSigQuit(std::ostream& os) {
  GetClassLinker()->DumpForSigQuit(os); // 让类加载部门提交报告
  GetInternTable()->DumpForSigQuit(os); // 让字符串管理部门提交报告
  GetJavaVM()->DumpForSigQuit(os); // 让JNI部门提交报告
  GetHeap()->DumpForSigQuit(os); // 让内存管理部门提交报告
  thread_list_->DumpForSigQuit(os); // 让所有工人队伍提交工作状态
  // 其他部门报告...
}

2.1 类加载部门的报告(ClassLinker)

类加载部门主管(位于 class_linker.cc)报告:
"我们从 Zygote 老厂继承了 4113 个类,建厂后又新增了 3239 个类,目前总共管理 7352 个类。"

cpp

运行

// class_linker.cc: 类加载报告
void ClassLinker::DumpForSigQuit(std::ostream& os) {
  os << "Zygote loaded classes=" << pre_zygote_class_table_.Size() 
     << " post zygote classes=" << class_table_.Size() << "\n";
}

2.2 字符串管理部门的报告(InternTable)

字符串管理部门主管(位于 intern_table.cc)报告:
"我们管理着 57550 个常用字符串(强引用)和 9315 个临时字符串(弱引用),方便工人们快速查找。"

cpp

运行

// intern_table.cc: 字符串管理报告
void InternTable::DumpForSigQuit(std::ostream& os) const {
  os << "Intern table: " << StrongSize() << " strong; " << WeakSize() << " weak\n";
}

2.3 内存管理部门的报告(Heap)

内存管理部门(位于 heap.cc)展示仓库状态:
"仓库总容量 40MB,已用 29MB(占 63%),目前存放了 207772 个物品(对象),近期垃圾清理情况如下...(GC 信息省略)"

cpp

运行

// heap.cc: 内存状态报告
void Heap::DumpForSigQuit(std::ostream& os) {
  os << "Heap: " << GetPercentFree() << "% free, " 
     << PrettySize(GetBytesAllocated()) << "/" << PrettySize(GetTotalMemory()) 
     << "; " << GetObjectsAllocated() << " objects\n";
  DumpGcPerformanceInfo(os); // 详细GC报告
}

第三章:工人队伍(线程)的工作状态普查

总经理最关心的是工人们的工作状态,线程管理部门(位于 thread_list.cc)开始逐一检查:

cpp

运行

// thread_list.cc: 工人队伍普查
void ThreadList::DumpForSigQuit(std::ostream& os) {
  os << "DALVIK THREADS (" << list_.size() << "):\n"; // 先报总数
  // 逐个工人检查
  for (每个线程) {
    Thread::DumpState(os, 线程, tid); // 检查工作状态
    DumpKernelStack(os, tid, "  kernel: ", false); // 查看在工厂外的工作记录
  }
}

3.1 主线程的工作记录

主线程(main)是车间的核心负责人,他的工作记录显示:
"我正在处理消息队列(MessageQueue.nativePollOnce),最近在 Looper.loop 循环中协调各部门工作,目前处于等待状态(state=S)。"

plaintext

"main" prio=5 tid=1 Native
  | sysTid=12078 nice=-2  // 工号12078,优先级较高
  | state=S  // 正在休息(等待消息)
  | schedstat=(5907843636 827600677 5112)  // 工作5907ms,等待827ms,切换5112次
  | stack=0x7fd64ef000-0x7fd64f1000  // 工作空间地址
  // 工作调用链(从内核到Java层)
  kernel: __switch_to+0x70/0x7c
  native: #05 pc ... Java_android_os_MessageQueue_nativePollOnce...
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.Looper.loop(Looper.java:135)
  ...

3.2 Binder 线程的工作记录

Binder 线程是车间之间的快递员,他的记录显示:
"我正在等待其他车间的快递任务(binder_thread_read),目前处于阻塞状态(state=S),已经处理了 50ms 用户态工作和 29ms 内核态工作。"

plaintext

"Binder_1" prio=5 tid=8 Native
  | sysTid=12092 nice=0  // 工号12092,普通优先级
  | state=S  // 等待快递任务
  | utm=50 stm=29  // 用户态工作500ms,内核态290ms
  | stack=0x7fa2647000-0x7fa2649000 stackSize=1013KB  // 工作空间
  // 内核工作记录
  kernel: binder_thread_read+0xd78/0xeb0
  // 本地方法调用链
  native: #02 pc ... _ZN7android14IPCThreadState14talkWithDriverEb+164
  (no managed stack frames)  // 没有Java层工作

第四章:报告生成与保存

所有部门的报告汇总后,由门卫 SignalCatcher 统一整理成完整的 Trace 文件,保存到工厂的公告栏(/data/anr/traces.txt):

cpp

运行

// signal_catcher.cc: 报告保存
void SignalCatcher::Output(const std::string& s) {
  int fd = open("/data/anr/traces.txt", O_APPEND | O_CREAT | O_WRONLY, 0666);
  // 写入报告内容
  file->WriteFully(s.data(), s.size());
  LOG(INFO) << "Wrote stack traces to '/data/anr/traces.txt'";
}

最终生成的报告开头如下:

plaintext

----- pid 888 at 2016-11-11 22:22:22 -----
Cmd line: system_server  // 车间名称
ABI: arm  // 工人使用的工具类型
Build type: optimized  // 工厂生产模式
Zygote loaded classes=4113 post zygote classes=3239  // 类加载情况
Intern table: 57550 strong; 9315 weak  // 字符串管理
Libraries: /system/lib/libandroid.so ... (16)  // 依赖的工具库
Heap: 27% free, 29MB/40MB; 307772 objects  // 内存使用
DALVIK THREADS (99):  // 总共有99个工人

故事背后的调试价值

当工厂出现 ANR(工人长时间不响应)时,这份 Trace 报告就是关键的诊断工具:

  1. 通过主线程的堆栈(Looper.loop)可以判断是否卡在消息处理中

  2. 查看各线程的 state 状态(S = 睡眠,R = 运行,D = 阻塞)判断是否有死锁

  3. schedstat 数据能分析线程的 CPU 占用情况(utm+stm = 总工作时间)

  4. 内核栈信息可以定位到 Linux 底层的阻塞点(如 epoll_wait、binder_ioctl)

通过这个故事,我们理解了 ART 处理 Trace 信号的完整流程:从信号捕获(SignalCatcher)到全局调度(Runtime),再到各模块数据收集,最终生成包含进程状态、内存使用、线程堆栈的详细报告,这些信息是分析 Android 应用卡顿、ANR 问题的重要依据。