Android 内存优化以及内存泄漏分析工具


假设有一个单例的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视图定位内存消耗

MAT中很多视图的第一行, 都可以输入正则, 来匹配我们关注的对象实例.

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"的图标, 点击进去可以看到内存泄露的疑点报告:

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM