运行时问题排查:编译、GC、类加载与内存
2026/8/9大约 3 分钟运行时Android 14运行时排障GC类加载
运行时问题排查:编译、GC、类加载与内存
前五章把运行时机制讲完了。这一章把它们串成排障链:启动慢、GC 卡顿、类找不到、 内存异常,按什么顺序查、看什么证据。
先确认 dexopt 状态和实际执行模式,再对齐 GC 日志、堆增长与暂停时间;类加载失败还要检查 ClassLoader 路径、验证错误和混淆结果。hprof 只适用于已经确认的 Java 堆问题。
先区分编译、执行、GC 与加载问题
| 常见误解 | 正确理解 |
|---|---|
| 运行时问题都是代码问题 | 编译策略、GC、类加载同样常见 |
| GC 卡顿看 logcat 就够 | 要结合堆用量与 hprof 定位根因 |
| 类找不到就是缺类 | 验证、混淆、依赖都可能 |
核心结论
- 排障顺序:编译状态 → 执行/GC → 类加载 → 内存。
- 启动慢先看编译状态与 profile。
- 卡顿与 GC 日志同步出现时,先查分配与泄漏。
- 类找不到按“声明、提供、验证”排查。
- 修复后回归:同场景对比编译、GC 与启动数据。
排障主链
逐步解释:
- 编译:确认 oat/profile 状态。
- 执行与 GC:看执行模式与 GC 日志。
- 类加载:查加载与验证。
- 内存:堆用量与泄漏。
常用命令与证据
环境:开发设备或模拟器;以下命令不需要 root。
adb shell cmd package compile -m speed-profile <包名>
adb shell dumpsys package <包名> | grep -E "codePath|primaryCpuAbi"
adb logcat -v threadtime | grep -E "GC_|concurrent copying|ClassNotFound|Verification"
adb shell dumpsys meminfo <包名> | grep -iE "dalvik|heap"
adb shell am dumpheap <包名> /data/local/tmp/heap.hprof| 证据 | 回答的问题 |
|---|---|
| 编译状态 | 启动路径是否最优 |
| GC 日志 | 回收是否频繁 |
| 类加载日志 | 类是否加载成功 |
| meminfo/hprof | 内存与泄漏 |
常见场景与定位思路
场景一:冷启动慢
先看编译状态与 profile,再对比 speed-profile/speed 策略下的启动数据。启动慢常与 编译不充分或主线程初始化相关。
场景二:运行期卡顿
看 GC 日志是否与掉帧同步;是则查分配过快与泄漏,否则查执行热点与锁。GC 与掉帧 同时出现是“内存问题”的强信号。
场景三:类找不到
按“声明方、提供方、验证”三步:确认类在哪个 dex、依赖是否就位、混淆与验证是否 影响。ClassNotFound 与 NoClassDefFound 要区分。
场景四:OOM
看堆上限与分配峰值,导出 hprof 找泄漏。OOM 多为堆上限问题,先找“谁占着不放”。
常见误区
- “运行时问题都是代码问题”:编译/GC/加载都可能。
- “GC 卡顿看 logcat 够”:要结合堆与 hprof。
- “类找不到就是缺类”:验证、混淆、依赖都可能。
- “OOM 就是设备内存不足”:多为堆上限问题。
延伸问题
- 如何建立启动与 GC 的性能基准?
- 编译策略与内存占用如何平衡?
- 运行时崩溃与 GC 的关联如何定位?
- 线上如何采集运行时诊断数据?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
art/dex2oat/dex2oat.cc | 编译 | oat 产物 |
art/runtime/gc/ | GC | 回收 |
art/runtime/class_linker.cc | 类加载 | 验证 |
am dumpheap | hprof | 泄漏 |
公共路径沿用本系列各章。行号以 r75 检索为准。
运行时之外的综合证据入口,见 系统调试与排障。