一、内存分析三步走:从现象到本质
当应用出现卡顿、OOM崩溃或内存曲线呈“阶梯式增长”时,就该请出内存分析神器——MAT(Memory Analyzer Tool)。整个分析流程可拆解为:
1. 捕获内存快照
通过adb shell am dumpheap <PID> /sdcard/heapdump.hprof命令生成堆转储文件,或使用Android Studio的Memory Profiler直接导出。
2. MAT工具初探
将.hprof文件拖入MAT,首先会看到概览面板:
- Actions:推荐先执行
Histogram(直方图)或Dominator Tree(支配树)分析 - OQL查询:支持类似SQL的语法,如
SELECT * FROM instanceof android.app.Activity
3. 关键指标解读
- Shallow Heap:对象自身占用的内存(不包含引用对象)
- Retained Heap:对象被回收后能释放的总内存(含其直接或间接引用的对象)
- GC Roots:从根节点(如静态变量、线程、JNI引用)出发的引用链
二、Dominator Tree:定位内存泄漏的“藏宝图”
1. 核心操作
- 在MAT中打开
Dominator Tree视图 - 按
Retained Heap降序排列 - 重点关注前几名的大对象(尤其是自定义类)
2. 典型泄漏模式
-
单例持有Activity:
MySingleton -> mContext -> MyActivity修复方案:单例中避免持有Activity,改用Application Context。
-
匿名内部类陷阱:
MainActivity$1 (Handler) -> MainActivity修复方案:使用静态内部类+WeakReference组合。
-
未注销的监听器:
SensorManager -> Listener -> MyActivity修复方案:在
onPause中调用unregisterListener。
三、OQL查询:内存问题的“CT扫描”
1. 常用查询模板
-
查找所有Activity实例:
sql SELECT * FROM INSTANCEOF android.app.Activity -
统计Bitmap数量:
sql SELECT COUNT(*) FROM INSTANCEOF android.graphics.Bitmap -
查找未关闭的Cursor:
sql SELECT * FROM android.database.Cursor WHERE isClosed() = false
2. 实战案例
某应用出现内存泄漏,通过OQL查询:
sql
SELECT s.toString(), COUNT(*) FROM INSTANCEOF java.lang.String s GROUP BY s ORDER BY COUNT(*) DESC
发现大量重复的"com.example.MyFragment"字符串,最终定位到Fragment未正确销毁。
四、线程分析:揪出内存抖动的“幕后黑手”
1. 线程视图入口
在MAT中打开Thread Overview,重点关注:
- 线程数量是否异常(正常应用线程数应<50)
- 是否存在大量
Runnable线程
2. 典型问题场景
-
线程池泄漏:
ThreadPoolExecutor -> Worker -> Runnable -> MyTask -> MyActivity修复方案:使用
Executors.newSingleThreadExecutor()替代手动管理线程。 -
AsyncTask未取消:
AsyncTask$1 -> MyActivity修复方案:在
onDestroy中调用cancel(true)。
五、进阶技巧:对比分析+路径追踪
1. 内存快照对比
- 生成操作前后的两个.hprof文件
- 在MAT中打开
Compare Basket视图 - 重点关注
Diff列变化,定位内存增长点
2. 路径追踪
-
右键点击可疑对象,选择
Path To GC Roots -
排除
Weak/Soft/Phantom引用,定位强引用链 -
示例路径:
MyActivity -> View -> Handler -> MessageQueue -> Looper -> Thread
六、总结:MAT工具的“降维打击”
MAT工具就像内存分析的“显微镜”,通过:
- Dominator Tree定位内存大户
- OQL查询精准扫描问题对象
- 线程分析揪出资源泄漏
- 对比分析量化内存变化
掌握这些技巧后,即使是复杂的内存泄漏问题也能迎刃而解。记住:内存优化没有银弹,但MAT绝对是你最趁手的“内存猎刀”