Android内存泄漏分析
- 什么是内存泄漏?
- 哪些情况会导致内存泄漏?
- 怎么分析查找内存泄漏?
一.什么是内存泄漏
Android垃圾回收采用的是根搜索算法(GC Root Tracing),从GC roots根节点开始对Heap进行遍历,搜索走过的路径叫做引用链(Reperence Chain),到最后,没有直接或间接被GC roots引用到的对象就是垃圾,需要被GC回收。 而内存泄露就是存在无效的引用,导致本应该被GC回收的对象无法被回收。
可以作为GCroot引用点的有
- 虚拟机栈(JavaStack)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- (本地方法)Native方法中JNI引用的对象
GC的作用区域的Java堆,只能回收堆内存中的对象。而方法区,本地方法区和栈不受GC控制,选这些区域的对象作为GC roots,被GC roots引用的对象不能被回收。
二.什么情况会造成内存泄漏
例子1:
private static Leak mLeak;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity.second);
mLeak = new Leak();
}
class Leak{
//...
}
mLeak是存储在静态区的静态变量,而Leak是内部类,它持有外部类Activity的引用。当外部Activity需要被销毁时,由于其引用被mLeak持有,系统不会对其进行GC,这样就造成了内存泄漏。
解决办法:可将内部类Leak设置为静态内部类。
static class Leak {
}
例子2:
class SingleInstance {
private static SingleInstance instance;
private Context context;
private SingleInstance(Context context) {
this.context = context;
}
public static getInstance(Context context){
synchronized(SingleInstance.class){
if(instance == null) {
instance = new SingleInstance(context);
}
}
return instance;
}
}
我们把Activity传入SingleInstance.getInstance方法,那么instance就持有了这个Activity引用。当instance没有被释放时,Activity就无法被GC,会一直存在,造成内存泄漏。
解决办法:可将new SingleInstance(context) 改为 new SingleInstance(context.getApplicationContext());这样便和传入的activity无关了
例子3:
Android中异步操作并时经常用Handler来传递结果,我们的代码通常会这么实现:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
其实这段代码有可能导致内存泄漏。
- 在Android中,当一个应用启动时,系统会自动创建一个供主线程使用的Looper实例。Looper的作用就是有一个一个的处理消息队列中的消息对象。在Android中,所有Android框架的事件(包括生命周期方法的调用以及按钮的点按事件等)都是放在消息中,然后加入到Looper要处理的队列中,由Looper一个一个的处理。主线程中的Looper的生命周期和当前应用一样长。
- 当一个Handler在主线程被初始化后,我们发送一条message作为Handler的消息到Looper中,实际上message中已经包含了Handler实例的引用。这样才能调用Handler的handlerMessage方法来完成消息的处理。
- 在JAVA中,非静态内部类或者是匿名内部类都会持有外部类的引用,静态的内部类不会持有外部类的引用。在上面的例子中,匿名内部类对象handler持有外部类SampleActivity的引用。
看下面代码就十分明显了:
public class SampleActivity extends Activity {
private final Handler mLeakHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
mLeakHandler.postDelay(new Runnable(){
@Override
public void run(){
//...
}
}, 60*1000*10);
finish(); // 结束Activity
}
}
当Activity.finish之后,被延迟的消息会被等待10分钟,由于消息持有handler实例的引用,而handler又是匿名内部类,其持有外部类Activity的引用,导致Activity无法被回收,activity的很多资源也无法被回收,造成activity内存泄漏。
注意,handler中的new Runnable()也是一个匿名内部类对象,同样持有外部类Activity的引用,导致Activity无法被回收。
解决办法:
要解决这种问题,一般使用静态内部类。因为静态内部类的实例不会持有外部类的引用,就不会造成Activity的内存泄漏。静态内部类中需要引用外部类对象时,可以使用弱引用来处理。同样,Runnable也要设置为静态的成员属性。代码修改如下:
public class SampleActivity extends Activity {
// Instance of static inner class do not hold the implicit reference
// of their outer class
private static class MyHandler extends Handler{
private WeakReference<SampleActivity> mActivity;
public MyHandler(Activity activity){
this.mActivity = new WeakReference<SampleActivity>(activity);
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
//...
}
}
}
private final MyHandler handler = new MyHandler(this);
private static final Runnale runnable = new Runnable() {
@Override
public void run(){
//...
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity.sample);
handler.postDelayed(runnable, 60*1000*10);
}
}
Android很多内存泄漏都是由于外部类调用了非静态内部类导致的,如果非静态内部类对象的生命周期大于Activity的生命周期,就会造成内存泄漏。推荐使用静态内部类+弱引用的方式来解决此类问题。
内存泄漏的检测
Android Studio运行应用程序,点击Android Monitors
在Memory一栏中,点击来执行一次gc,点击
可进入HPROF Viewer界面,来查看JAVA的heap,如下图
点击Analyzer Tasks后系统会自动分析出内存泄漏的Activity,在Reference Tree中查看持有该对象的引用。