假设有一个单例的ListenerManager, 可以add / remove Listener, 有一个Activity, 实现了该listener, 且这个Activity中持有大对象BigObject, BigObject中包含一个大的字符串数组和一个Bitmap List.
代码片段如下:
ListenerManager
1 public class ListenerManager { 2 3 private static ListenerManager sInstance; 4 private ListenerManager() {} 5 6 private List<SampleListener> listeners = new ArrayList<>(); 7 8 public static ListenerManager getInstance() { 9 if (sInstance == null) { 10 sInstance = new ListenerManager(); 11 } 12 13 return sInstance; 14 } 15 16 public void addListener(SampleListener listener) { 17 listeners.add(listener); 18 } 19 20 public void removeListener(SampleListener listener) { 21 listeners.remove(listener); 22 } 23 }
MemoryLeakActivity
1 public class MemoryLeakActivity extends AppCompatActivity implements SampleListener { 2 3 private BigObject mBigObject = new BigObject(); 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_memory_leak); 9 10 ListenerManager.getInstance().addListener(this); 11 } 12 13 @Override 14 public void doSomething() { 15 16 } 17 }
BigObject
1 public class BigObject { 2 3 private ArrayList<Bitmap> bitmaps = new ArrayList<>(); 4 private String[] values = new String[1000]; 5 6 public BigObject() { 7 8 for (int i = 0; i < 20; i++) { 9 bitmaps.add(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)); 10 } 11 12 for (int i = 0; i < 1000; i++) { 13 values[i] = "value:" + i; 14 } 15 }
1. Memory Monitor
Memory Monitor 是 Android Studio内置的, 官方的内存监测工具. 图形化的展示当前应用的内存状态, 包括已分配内存, 空闲内存, 内存实时动态等.
-
图形区域:
- 横向时间轴, 内存检测时间, 跟随滚动.
- 纵向内存轴, 内存使用量, 根据应用使用动态分配.
- 蓝色区域表示当前已分配使用的内存量.
- 灰色区域表示剩余可使用的内存量.
- 红色圈圈指示的是系统GC事件(内存有一定量的回收).
-
工具栏:
- ① GC按钮, 点击执行一次GC操作.
- ② Dump Java Heap按钮, 点击会在该调试工程的captures目录生成一个类似这样"com.anly.githubapp_2016.09.21_23.42.hprof"命名的hprof文件, 并打开Android Studio的HPROF Viewer显示该文件内容.
- ③ Allocation Traking按钮, 点击一次开始, 再次点击结束, 同样会在captrures目录生成一个文件, 类似"com.anly.githubapp_2016.09.21_23.48.alloc", alloc后缀的文件, 并打开Allocation Tracker视图展示该文件内容.
1.1 查看Memory使用, 并导出hprof文件
Memory Monitor通过Dump Java Heap可以生成一个hprof的文件, 这个文件是Android特定的Heap和CPU分析文件, 记录了这段时间内的Java Heap变化.
关于Java Heap
由Java Heap文件可以看到如下数据:
- 按类型显示对象申请的内存快照(内存大小);
- 每次自动或手动触发GC时的样本数据;
- 协助定位可能发生的内存泄露点:
- 所有已经被destroyed的activity, 还可以从GC Root访问到.
- 重复的String实例.
启动我们要检测的Activity(MemoryLeakActivity), 然后退出, 在monitor中查看内存变化:
1.2 在HPROF Viewer界面, 开始分析
可点击选择Heap类型, Heap类型分为:
- App Heap -- 当前App使用的Heap
- Image Heap -- 磁盘上当前App的内存映射拷贝
- Zygote Heap -- Zygote进程Heap(每个App进程都是从Zygote孵化出来的, 这部分基本是framework中的通用的类的Heap)
第一步
点击"Analyzer Tasks"视图中的启动按钮, 启动分析
分析任务包括:
- 检测泄露的Activity
- 查找重复的String实例
第二步
查看"Analysis Result"中的分析结果, 点击"Leaked Activityes"中的具体实例, 该实例的引用关系将会展示在"Reference Tree"视图中.
第三步
根据"Reference Tree"视图中的引用关系找到是谁让这个leak的activity活着的, 也就是谁Dominate这个activity对象.
此例中, 比较简单, 可以很清晰看到是ListenerManager的静态单例sInstance最终支配了MemoryLeakActivity. sIntance连接到GC Roots, 故而导致MemoryLeakActivity GC Roots可达, 无法被回收.
1.3 使用Heap Viewer查看内存消耗
上述步骤, 可以让我们快速定位可能的内存泄露. 当然, 内存问题, 除了内存泄露, 还有内存消耗过大. 我们可以在Heap Viewer中查看分析内存的消耗点, 如下:
4, MAT
Eclipse MAT是一个快速且功能丰富的Java Heap分析工具, 可以帮助我们寻找内存泄露, 减少内存消耗.
MAT可以分析程序(成千上万的对象产生过程中)生成的Heap dumps文件, 它会快速计算出对象的Retained Size, 来展示是哪些对象没有被GC, 自动生成内存泄露疑点的报告.
MAT. 相对与Android Studio中的Memory Monitor, HPROF工具来说, MAT的使用显得更加生涩, 难以理解.
关于MAT的帮助文档, 个人翻译了一份, 需要的同学戳这里.
当然, 如果我们想对内存的使用相关知识了解得更多, 还是有必要了解下MAT的...
下面我们以几个角度来了解下MAT的基本使用:
再次:
Android Studio导出的hprof文件需要转换下才可以在MAT中使用.
1 $ hprof-conv com.anly.samples_2016.10.31_15.07.hprof mat.hprof
4.1 Histogram视图定位内存消耗
4.2 Dominate Tree视图查看支配关系
4.3 使用OQL查询相关对象
对于Android App开发来说, 大部分的内存问题都跟四大组件, 尤其是Activity相关, 故而我们会想查出所有Activity实例的内存占用情况, 可以使用OQL来查询:
具体OQL语法看这里.
4.4 GC路径定位问题
上面几个视图都可以让我们很快速的找到内存的消耗点, 接下来我们要分析的就是为何这些个大对象没有被回收.
根据第一弹:GC那些事儿所言, 对象没有被回收是因为他有到GC Roots的可达路径. 那么我们就来分析下这条路径(Path to GC Roots), 看看是谁在这条路中"搭桥".
如下, 进入该对象的"path2gc"视图:
同样, 与HPROF Analyzer异曲同工, 找出了是ListenerManager的静态实例导致了MemoryLeakActivity无法回收.
5. LeakCanary让内存泄露无处可藏
大道至简, 程序员都应该"懒", 故而我们都希望有更方便快捷的方式让我们发现内存泄露. 伟大的square发挥了这一优良传统, LeakCanary面世.
5.1 加入LeakCanary
app的build.gradle中加入:
1 dependencies { 2 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' 3 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' 4 testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' 5 }
Application中加入:
1 public class SampleApplication extends Application { 2 3 @Override 4 public void onCreate() { 5 super.onCreate(); 6 7 LeakCanary.install(this); 8 } 9 }
4.2 操作要检测的界面, 查看结果
当发生可疑内存泄露时, 会在桌面生成一个"Leaks"的图标, 点击进去可以看到内存泄露的疑点报告: