第1章:泄漏现象识别
泄漏的第一道防线是“现象”。在OkHttp与Retrofit组合场景中,泄漏不会立即崩溃,而是通过一组可量化的异常信号逐步暴露。本章帮你建立四类核心现象的识别能力,从崩溃日志、GC轨迹、内存走势、线程快照中快速判定是否存在泄漏。
1.1 现象总览与判断规则
四类现象各有明确阈值,只要同时满足任意两类即可高度怀疑存在泄漏。下表列出每类现象的典型表现与判定标准。
| 现象编号 | 现象名称 | 典型表现 | 判定标准 |
|---|---|---|---|
| P1 | OOM崩溃 | App闪退,日志含 OutOfMemoryError | 连续3天内≥1次OOM,且堆栈指向OkHttp/Retrofit相关类 |
| P2 | GC频繁 | Logcat 中 GC 日志间隔 < 5s,且多次 Full GC | 同一进程每分钟GC次数 > 6,且 Concurrent Mark Sweep 耗时 > 500ms |
| P3 | 内存占用持续增长 | Profiler 中 Java Heap 每30分钟增幅 > 10% 且不回落 | 连续2小时堆内存无回收,曲线呈阶梯上升 |
| P4 | 线程数异常 | adb shell dumpsys thread 显示 OkHttp Dispatcher 线程 > 64 | 核心线程数持续 > 32,且非高峰时段不下降 |
1.2 OOM崩溃:最直接的报警
OOM发生时,Logcat 中通常包含如下关键行:
java.lang.OutOfMemoryError: Failed to allocate a 65536 byte allocation with 0 free bytes and 64KB until OOM
at okhttp3.internal.http2.Http2Connection$ReaderRunnable.onSettings(Http2Connection.java:892)
at okhttp3.internal.http2.Http2Reader.readSettings(Http2Reader.java:408)
...
注意堆栈顶部是否为 OkHttp 或 Retrofit 的读写/分发类。若未关闭 Response 或 Call 对象,OkHttp 内部会持有大量 byte[] 和 Socket,最终撑爆堆。
1.3 GC频繁:泄漏的早期心跳
泄漏对象无法被回收,导致新生代迅速填满,GC 被迫高频执行。在 Android Studio Profiler 或 adb logcat -v time | grep GC 中可观测到:
D/dalvikvm: GC_CONCURRENT freed 12K, 1% free 1024K/2048K, paused 15ms+13ms
D/dalvikvm: GC_FOR_ALLOC freed 3K, 1% free 1025K/2048K, paused 15ms
D/dalvikvm: GC_CONCURRENT freed 8K, 1% free 1026K/2048K, paused 12ms+11ms
...
若 1 分钟内出现 6 次以上 GC_CONCURRENT/FOR_ALLOC,且堆内存始终不降,说明泄漏对象已阻塞正常回收路径。
1.4 内存占用持续增长:用数据说话
使用 Android Studio Memory Profiler 或 dumpsys meminfo 可绘制曲线。下图展示了典型的泄漏曲线:
健康应用在操作结束后内存应回落到基线(绿色虚线);泄漏应用则呈现红色阶梯,直至触发OOM。
1.5 线程数异常:OkHttp Dispatcher 失控
OkHttp 默认使用线程池处理异步请求。若 Call/Response 未正确关闭,Dispatcher 会不断创建新线程而不销毁。通过以下命令快速检查:
adb shell dumpsys thread | grep "okhttp" | wc -l
若结果 > 32,请进一步查看线程名称分布:
adb shell dumpsys thread | grep -E "OkHttp (Dispatcher|ConnectionPool)"
"OkHttp Dispatcher" prio=5 tid=1 Blocked
"OkHttp Dispatcher" prio=5 tid=2 Blocked
"OkHttp ConnectionPool" prio=5 tid=3 TimedWaiting
...
正常场景下,Dispatcher 线程只会在请求活跃期间存在,空闲后应迅速回收。若阻塞线程数持续增长,基本可以判定存在泄漏。
1.6 故障树:现象到泄漏的判定流程
以下 SVG 故障树帮助你从多个现象中快速定位泄漏根因:
1.7 快速自查表
现场排查时可对照下表逐项检查:
| 检查项 | 命令/工具 | 异常阈值 |
|---|---|---|
| OOM记录 | Logcat / Firebase Crashlytics | 最近3天≥1次,堆栈指向OkHttp/Retrofit |
| GC频率 | adb logcat -v time | grep -E "GC_CONCURRENT|GC_FOR_ALLOC" |
每分钟 > 6 次,持续超过5分钟 |
| 堆内存走势 | Android Studio Profiler / dumpsys meminfo |
每30分钟增幅 > 10% 且不回落 |
| 线程数 | adb shell dumpsys thread | grep "OkHttp" |
活跃线程数 > 32 |
adb shell am broadcast -a android.intent.action.DISMISS_KEYGUARD),然后观察内存是否回到初始值。若不回落到基线,泄漏已存在。