內存泄漏簡介
Java可以保證當沒有引用指向對象的時候,對象會被垃圾回收器回收,與c語言自己申請的內存自己釋放相比,java程序員輕鬆了很多,但是並不代表java程序員不用擔心內存泄漏。當java程序發生內存泄漏的時候往往具有隱蔽性。
定義
內存泄漏用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該內存單元。直到程序結束。從程序員的角度來看“內存泄漏”,其實就是一個對象的生命週期超出了程序員所預期的長度,那麼這個對象就泄漏了。
Android開發中的內存泄漏
Android應用程序本身系統分配的內存很少,一旦發生泄漏,程序很快就會變得非常卡頓,直至OOM崩潰。本文將通過兩個案例,來介紹內存泄漏分析工具MAT,以及內存分析的技巧。
需要工具
準備內存泄漏的分析工具,可以安裝eclipse插件mat。如果使用AndroidStudio開發,可以將hprof文件導出再另外使用mat進行分析,不過會缺少某些功能,但是也夠用了,本文適應的場景是AndroidStudio+MAT。
Android開發中常見的內存泄漏
人爲製造一個內存泄漏
自定義一個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具體命令下圖:
經轉換得到的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檢查反註冊,內存泄漏的原因很快就可以搞清楚了。
鏈接
Eclipse Mat 工具下載地址
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,然後回調相應的消息處理函數。
如果循環體中有消息未處理(Message排隊中),那麼Handler會一直存在,那麼Handler的外部類(通常是Activity)的引用計數一直不會是0,所以那個外部類就不能被垃圾回收。很多人會遇到activity的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;
}
}