这里我们通过一个例子来学习Memory Monitor工具的使用。
package com.android.test;
import android.content.Context;
public class UserManger {
public static UserManger instance;
private Context mContext;
private UserManger(Context context) {
mContext = context;
}
public static UserManger getInstance(Context context) {
if (instance == null) {
instance = new UserManger(context);
}
return instance;
}
}
package com.android.test;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
UserManger mUserManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUserManager = UserManger.getInstance(this);
}
}
我们在之前的学习中有说过,
内存泄漏产生的原因:当一个对象已经不需要再使用了,本该被回收时,但是因为有另外一个对象持有它的引用,从而导致对象不能被回收,这种导致本该被回收的对象不能被回收而停留在堆内存中,就会产生内存泄漏。
上面的代码就是一个单例模式内存泄漏的场景,那么这篇博客的目的就是如何把代码中的内存泄漏找出来。
分析上面的代码,我们知道发生泄漏的不是UserManager,而是MainActivity,知道为什么吗?因为UserManager中有一个静态成员instance,其生命周期和应用程序的生命周期是一致的,当退出应用时,才会被销毁;当MainActivity退出时,有可能发生GC,GC时就会回收MainActivity,因为MainActivity的对象(this)被UserManager对象所引用,UserManager本身是不能被干掉的,所有就会发生内存泄漏。
Android Studio自导的Memory Monitor可以方便的观察堆内存的分配情况,并且可以粗略的观察有没有发生Memory Leak。
在Android Studio界面,双击“Shift”键,打开搜索框,输入“Android Monitor”,即可打开Android Monitor界面。
Memory Monitor是Android Monitor中的一种,位于界面中最上面。
回到我们的程序,多点击几次GC按钮,看一下这个用于的内存使用情况。
可以看到现在已经分配的内存有5.69MB,我把手机旋转一下,再点击GC按钮。
可以看到现在的内存使用量是6.32MB,却明显增加了,这里很关键!!
接下来,我们找一下哪里发生了内存泄漏。
点击Dump Java Heap,生成快照文件com.android.test_2018.02.01_16.14.hprof,Android Studio会自动弹出HPROF Viewer来分析它。
下面引用网络一张图片,介绍下HPROF Viewer使用。
HPROF Viewer查看方式
左上角两个红框,是可选列表,分别是用来选择Heap区域和Class View的展示方式。
Heap类型分为:
App Heap:当前App使用的Heap。
Image Heap:磁盘上当前APP的内存映射拷贝。
Zygote Heap:Zygote进程Heap(每个APP进程都是从Zygote孵化出来的,这部分基本是framework中的通用类的Heap)。
Class List View:类列表方式。
Package Tree View:根据包结构的树状显示。
HPROF Viewer主要分为ABC三大板块
板块A:这个应用中所有类的名字。
版块B:左边类的所有实例。
板块C:在选择B中的实例后,这个实例的引用树。
A板块左上角列名解释
B板块右上角上角列名解释
列名 | 解释 |
---|---|
Instance | 该类的实例 |
Depth | 深度, 从任一GC Root点到该实例的最短跳数 |
Dominating Size | 该实例可支配的内存大小 |
在这个界面中可以直接把内存泄露可能的类找出来。
1. 一个Activity应该只有一个实例,但是从A区域来看total count的值为2,heap count的值也为2,说明有一个是多余的。
2. 在B区域中可以看见两个MainActivity的实例,点击一个看他的引用树情况。
3. 在C区域中可以看到MainActivity的实例Context,被UserManager的instance引用了,引用深度为1。
4. 在Analyzer Tasks 区域中,直接告诉你Leaked Activities,MainActivity包含其中。
多方面的证据表明MainActivity发生了内存泄露。
public class UserManger {
public static UserManger instance;
private Context mContext;
private UserManger(Context context) {
mContext = context;
}
public static UserManger getInstance(Context context) {
if (instance == null) {
if (context != null) {
instance = new UserManger(context.getApplicationContext());
}
}
return instance;
}
}
不要用Activity的Context,因为Activity随时可能被回收,我们可以使用Application的Context,Application的Context的生命周期是整个应用。
Memory Monitor获得内存的动态视图,Heap Viewer显示堆内存中存储了什么,但是Heap Viewer不能显示你的数据具体分配在代码的何处。还有一个功能Allocation Tracker,用来内存分配追踪。
点击图中Allocation Tracking按钮启动追踪,再次点击停止追踪,随后自动生成一个alloc结尾的文件,这个文件就记录了这次追踪到的所有数据,然后会自动打开一个数据面板。
Allocation Tracker查看方式
有两种查看方式,默认是Group by Method方式。
Group by Method:用方法来分类我们的内存分配。
Group by Allocator:用内存分配器来分类我们的内存分配。
从上图可以看出,首先以线程对象分类,Size是内存大小,Count是分配了多少次内存,点击就可以查看到每个线程里所有分配内存的方法。
关于性能优化内存相关的更多了解可参考:
Android内存优化--OOM
Android性能优化 -- 内存管理机制