内存泄漏简介
Java可以保证当没有引用指向对象的时候,对象会被垃圾回收器回收,与c语言自己申请的内存自己释放相比,java程序员轻松了很多,但是并不代表java程序员不用担心内存泄漏。当java程序发生内存泄漏的时候往往具有隐蔽性。
定义
内存泄漏用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。从程序员的角度来看“内存泄漏”,其实就是一个对象的生命周期超出了程序员所预期的长度,那么这个对象就泄漏了。
Android开发中的内存泄漏
Android应用程序本身系统分配的内存很少,一旦发生泄漏,程序很快就会变得非常卡顿,直至OOM崩溃。本文将通过两个案例,来介绍内存泄漏分析工具MAT,以及内存分析的技巧。
需要工具
准备内存泄漏的分析工具,可以安装eclipse插件mat。如果使用AndroidStudio开发,可以将hprof文件导出再另外使用mat进行分析,不过会缺少某些功能,但是也够用了,本文适应的场景是AndroidStudio+MAT。
Android开发中常见的内存泄漏
•对象没有反注册
•数据库cursor没有关闭
•Bitmap没有回收
•ListViewitem没有复用
•Handler在Activity中定义为非static的匿名内部类
人为制造一个内存泄漏
自定义一个ActivityManager,提供两个方法,分别用来注册与反注册Activity
package com.example.qinghua_liu.myapplication;
importandroid.app.Activity;
importandroid.util.Log;
importjava.util.ArrayList;
importjava.util.List;
publicclass ActivityManager{
private List<Activity>mActivities=newArrayList<>();
private static ActivityManagersInstance;
private ActivityManager(){
};
public static ActivityManagerinstance() {
if (sInstance==null){
sInstance=newActivityManager();
}
return sInstance;
}
public void registActivity(Activityactivity) {
mActivities.add(activity);
Log.d("TAG","mActivities.size()="+mActivities.size());
}
public void unRigistActivity(Activityactivity) {
mActivities.remove(activity);
}
}
在MainActivity的onCreate与onDestroy中分别调用registActivity和registActivity方法进行注册与反注册。但是Main2Activity忘记了反注册
public classMainActivityextendsAppCompatActivity{
Button mBtn;
@Override
protectedvoid onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);mBtn = (Button)findViewById(R.id.mybutton);
mBtn("newActivity");
mBtnView.OnClickListener(){
@Override
public void onClick(Viewv) {
Intent it = newIntent(MainActivity.this,
Main2Activity.class);
try {
startActivity(it);
} catch (Exceptione) {
Log.e(TAG,e.toString());
}
}
});
ActivityManager.instance().registActivity(this);}
@Override
protectedvoid onDestroy(){
super.onDestroy();
ActivityManager.instance().unRigistActivity(this);}}
public classMain2ActivityextendsActivity{
private Object[] mObjs= new Object[10000];
@Override
protectedvoid onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//模拟快速消耗大量内存,使效果明显
for (inti=0;i<mObjs.length;i++){
mObjs[i] =new Object();
}ActivityManager.instance().registActivity(this);
}
@Override
protectedvoid onDestroy(){
super.onDestroy();
//ActivityManager.instance().unRigistActivity(this);// if forget unRigist
}}
AndroidStudio Monitor Debug
Monitor界面可以大概显现内存,CPU,GPU,NetWork的占用消耗情况。
详细分析还要依靠Mat。APP操作多次叫起和销毁MainActivity以及Main2Activity。
导出Memory hprof 文件:
在AS Monitor Debug界面按DumpJava Heap,工具会自动生成一份hprof后缀文件。
hprof文件分析
Android Studio 生成的hprof文件 MAT还不能直接使用,需用转换成标准的hprof文件。
转换可借助AndroidSdkPlatform toolhprof具体命令下图:
MAT分析工具
经转换得到的hprof文件可以由MAT直接打开了。
MAT分析
Mat会dump一个内存快照出来,然后从分析报告中点击“Leaksuspects”这里会列出可能泄漏的对象,其中你会发现“com.example.qinghua_liu.myapplication.Main2Activity”的身影。Main2Activity这个类有49个实例,CodeAuthor应该一下子就会发现,原来是Main2Activity泄漏了。发现它泄漏之后,如何找出是哪一个对象持有了Main2Activity对象的引用呢?
找出引用链
使用OQL对象查询语言查询出泄漏的对象,和SQL非常相似,语法简单易懂,却非常强大。select* from com.example.qinghua_liu.myapplication.Main2Activity筛选出Main2Activity这一类对象。
然后选择“excludeweak/soft references”筛选出除了软引用和弱引用之外(即强引用)的对象。
然后找出的GC的Root,从下图可以看出,原来Activity对象被ActivityManager里面的ArrayList给hold住了,所以接下来的工作就是在Main2Activity检查反注册,内存泄漏的原因很快就可以搞清楚了。
链接
Handler使用过程中可能引发的内存泄漏
我们UI编程时一般会使用Handler进行UI更新,环境会有下面一段温馨的提示:ThisHandler class should be static or leaksmightoccur
另外还有段详细英文描述,大概意思就是:
一旦Handler被声明为内部类,那么可能导致它的外部类不能够被垃圾回收。如果Handler是在其他线程(通常是workerthread)使用Looper或MessageQueue(消息队列),而不是main线程(UI线程),那么就没有这个问题。如果Handler使用Looper或MessageQueue在主线程(mainthread),你需要对Handler的声明做如下修改:
声明Handler为static类;在外部类中实例化一个外部类的WeakReference(弱引用)并且在Handler初始化时传入这个对象给你的Handler;将所有引用的外部类成员使用WeakReference对象。(与Bitmap工作线程弱引用相似?)
解决方案
private static class CopyFileHandlerextendsHandler{
WeakReference<MainActivity>mActivity;
public CopyFileHandler(MainActivityactivity) {
mActivity=newWeakReference<>(activity);
}
public void handleMessage(Messagemsg){
final MainActivityactivity =mActivity.get();
if(null!=activity&&null!=activity.btn){
//handle you message here!
activity.btn.setText("clickme");
activity.btn.setClickable(true);
} }
}
private CopyFileHandlermHandler;
@Override
protected void onCreate(BundlesavedInstanceState){
mHandler=newCopyFileHandler(this);
}
private void startCopyFileThread(){
Log.d(TAG,"startCopyDBThread");
new Thread(newRunnable() {
@Override
public void run(){
//DOSOMETHING LIKE: copyDBFile();
Messagemsg=mHandler.obtainMessage();
mHandler.sendMessageDelayed(msg,1000); }
}).start();
}
为什么会内存泄漏
这与几个关键词有关:内部类、Handler的消息循环(Looper)、Java垃圾回收机制。需要强调一下,并不是每次使用Handler都会引发内存泄漏,这里面有一定的几率,需要满足特定条件才会引起泄漏。
内部类会有一个指向外部类的(硬)引用。垃圾回收机制中约定,当内存中的一个对象的引用计数为0时,将会被回收。
Handler作为Android上的异步消息处理机制,它的工作是需要Looper和MessageQueue配合的。简单的说,要维护一个循环体(Looper)处理消息队列(MessageQueue)。每循环一次就从MessageQueue中取出一个Message,然后回调相应的消息处理函数。
onDestroy方法一直不执行就是这个原因。
另一个解决方案的尝试
提示描述中提到了也可以让Handler在workerthread中使用Looper或MessageQueue。
private class WorkThreadextendsThread{
@Override
public void run(){
super.run();
Looper.prepare();
testHandler=newHandler(){
publicvoid handleMessage(Messagemsg){ mybutton1.post(newRunnable(){
@Override
publicvoid run(){
mybutton1.setText("clickme");
mybutton1.setClickable(true);
}
});
}
};
Looper.loop();
}
}
private WorkThreadmThread;
@Override
protectedvoid onCreate(BundlesavedInstanceState){ mThread=newWorkThread();
if(Thread.State.NEW==mThread.getState()){
mThread.start();
}}
@OnClick({R.id.hello,R.id.mybutton1,R.id.radioButton})
publicvoid onClick(Viewview) {
switch (view.getId()){
case R.id.mybutton1: mybutton1.setText("clicked");
mybutton1.setClickable(false);
testHandler.sendEmptyMessageDelayed(1,3000); break;
case R.id.radioButton:
break;
}
}