Skip to content

Latest commit

 

History

History
181 lines (121 loc) · 6.11 KB

File metadata and controls

181 lines (121 loc) · 6.11 KB

从一次“Java 堆不高但 PSS 爆了”的排查说起:Memory Lab Demo 全链路实战

配套仓库:Android-App-Memory-Analysis
配套 Demo:demo/memory-lab

封面建议:问题现象、采集命令、结论三段式排版。

1. 问题为什么难

Android 内存问题最难的点,不是“看不到数据”,而是“数据之间对不上”。

  • dumpsys meminfo 能看方向(Java/Native/Graphics),但很难定位到对象级根因。
  • HPROF 能看到对象和持有链,却解释不了很多 Native 增量。
  • showmap/smaps 能看到映射增长,但不直接告诉你是哪段业务行为触发。

这篇文章的目标是把三套视角变成一条证据链:业务操作 -> 指标变化 -> 根因归属

2. Demo 设计思路

Memory Lab 不是功能 Demo,而是“故障注入器”。它把常见内存问题做成按钮化场景,保证可重复触发、可重复采集。

当前内置场景:

  1. Java leak + collections
  2. Bitmap duplicates + large bitmap
  3. DirectByteBuffer native pressure
  4. JNI malloc/mmap native pressure
  5. Ashmem pressure
  6. Thread stack pressure
  7. WebView native pressure
  8. View storm
  9. UI jank storm
  10. Fragment/Service/BroadcastReceiver leak set
  11. Business LruCache misconfiguration

首页提供 0) One-click trigger all scenarios,可以一次性触发主要泄漏与压力模式。

实现入口:demo/memory-lab/app/src/main/java/com/androidperformance/memorylab/MainActivity.java

图示建议:场景触发页(可放 MainActivity 按钮分组截图)。

3. 一套可落地的采集流程

3.1 准备变量

PKG=com.androidperformance.memorylab
TS=$(date +%Y%m%d_%H%M%S)
OUT=./captures/$TS
REMOTE_HPROF=/data/local/tmp/memory_lab_$TS.hprof

adb root
adb wait-for-device

PID=$(adb shell pidof $PKG)
mkdir -p "$OUT"

3.2 采集核心证据

adb shell showmap $PID > "$OUT/showmap.txt"
adb shell su -c "cat /proc/$PID/smaps" > "$OUT/smaps.txt"
adb shell dumpsys meminfo -d $PKG > "$OUT/meminfo.txt"
adb shell dumpsys gfxinfo $PKG > "$OUT/gfxinfo.txt"
adb shell am dumpheap $PKG $REMOTE_HPROF
adb pull $REMOTE_HPROF "$OUT/heap.hprof"
adb shell rm $REMOTE_HPROF

脚本版同样可用:demo/memory-lab/scripts/capture_memory_lab.sh

3.3 分析命令

python3 analyze.py panorama -d "$OUT"

python3 analyze.py combined --modern \
  --hprof "$OUT/heap.hprof" \
  --smaps "$OUT/smaps.txt" \
  --meminfo "$OUT/meminfo.txt" \
  --json-output "$OUT/report.json"

图示建议:panorama 输出中的“内存概览 + 异常列表”区域。

4. 一次真实跑数(示例)

demo/memory-lab/captures/latest 为例:

  • Total PSS:329.87 MB
  • Native Heap:78.93 MB
  • Graphics:55.83 MB
  • WebViews:4
  • Panorama 异常:UNTRACKED_NATIVEHIGH_JANKTOO_MANY_VIEWS

Combined 分析同时给出:

  • LeakySingletons 的多类静态字段形成大规模保活(Java bucket、业务 LruCache、DirectBuffer、Fragment/Service/Receiver)
  • 大尺寸 + 重复尺寸 Bitmap(820x544 反复出现)
  • 低利用率大 HashMap 与空集合大量堆积
  • DirectByteBuffer 持有链能追溯到 LeakySingletons

这类输出最有价值的地方是:你能把同一次操作,在三套数据源里看到一致变化,而不是“单点猜测”。

5. 场景与信号映射(排障时最实用)

场景 A:Java leak + collections

  • 看点:Java Heap、对象持有链、集合容量利用率
  • 证据:meminfo 的 Java Heap + HPROF 的 Retained/holder chain

场景 B:Bitmap duplicates + large bitmap

  • 看点:Bitmap 数量、重复尺寸、Graphics 增量
  • 证据:HPROF Bitmap 深度分析 + meminfo/smaps 图形口径

场景 C:DirectByteBuffer + JNI native

  • 看点:Native Heap 快速上涨,但 Java 对象无法完全解释
  • 证据:smaps/showmap Native 映射 + HPROF DirectByteBuffer 持有链

场景 D:Thread stack pressure

  • 看点:Stack 区域是否同步增长
  • 证据:meminfo Stack + smaps stack_and_tls

场景 E:WebView native pressure

  • 看点:WebView 数量与 Native 增量耦合
  • 证据:Objects.WebViews + Native Heap + WebView 相关映射

6. 当前覆盖边界:已经覆盖了什么,还缺什么

先说结论:当前 Demo 已覆盖主线问题(Java 泄漏、Bitmap、Native/JNI、DirectBuffer、Ashmem、线程栈、WebView),但还不是“全口径满覆盖”。

6.1 已覆盖(强)

  • Java 泄漏与集合累积
  • Bitmap 大图与重复图
  • Native 未追踪增长(JNI + DirectByteBuffer)
  • WebView 导致的 Native 压力
  • 基础 Ashmem 与线程栈场景

6.2 仍是弱覆盖或未覆盖

  1. Tracked Bitmap 口径受 ROM 输出限制
    meminfo -d 在部分机型没有完整 Native Allocations 段,导致 Bitmap (malloced/nonmalloced) 统计不稳定。

  2. 高阈值场景并非每次都能稳定打穿
    high_dalvik_memoryhigh_native_memoryhigh_graphics_memoryhigh_jit_cache 这类阈值并非每次都能触发。

  3. 系统侧内存上下文未纳入案例主流程
    proc_meminfozram/swapdmabuf 已被工具支持,但 Demo 教程里还没有固定采集与解释模板。

7. 建议补齐路线(按收益排序)

  1. 增加 mode 参数(light/medium/heavy),保证不同设备上阈值触发稳定。
  2. 增加 ROM 能力探测,区分是否支持完整 Native Allocations
  3. 扩展采集模板,固定纳入 proc_meminfozram_swapdmabuf

8. 复盘模板(团队实践)

每次演练建议沉淀 6 个字段:

  • 触发步骤
  • before/after 时间点
  • meminfo 关键字段变化
  • smaps/showmap 前 3 个增长映射
  • HPROF 前 3 条关键持有链
  • 修复动作与复验结果

图示建议:团队复盘模板(触发步骤 / 关键指标 / 复验结果)。

9. 总结

这套 Demo 的价值,不是“模拟所有线上问题”,而是建立一种稳定工作流:

先用 meminfo 定方向 -> 再用 smaps/showmap 锁区域 -> 最后用 HPROF 落对象。

当你把这个流程变成团队共识,内存排障就不再是经验主义,而是可复现的工程过程。