第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 可绘制曲线。下图展示了典型的泄漏曲线:

内存占用持续增长曲线 时间 堆内存(MB) T0 T1 T2 T3 T4 OOM阈值 健康基线(平稳) 泄漏曲线特点:每个业务高峰后内存不回落,整体趋势持续上升

健康应用在操作结束后内存应回落到基线(绿色虚线);泄漏应用则呈现红色阶梯,直至触发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 故障树帮助你从多个现象中快速定位泄漏根因:

现象识别故障树 泄漏现象识别触发 OOM崩溃 GC频繁 内存持续增长 线程数异常 判定规则 同时满足任意2个现象 → 高度怀疑泄漏 → 启动数据采集

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
实战提示:进入应用后立即执行一次GC(adb shell am broadcast -a android.intent.action.DISMISS_KEYGUARD),然后观察内存是否回到初始值。若不回落到基线,泄漏已存在。
本章核心结论:OOM崩溃 + 线程数异常 组合出现时,泄漏概率 > 90%。无需等待完整Profiling,可立即进入数据采集阶段。