目錄:
一魏颓、內(nèi)存泄漏介紹
二愁溜、常見內(nèi)存泄漏場(chǎng)景
??????1.單例導(dǎo)致內(nèi)存泄露
??????2.靜態(tài)變量導(dǎo)致內(nèi)存泄漏
??????3.非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄露
??????4.未取消注冊(cè)或回調(diào)導(dǎo)致內(nèi)存泄露
??????5.Timer和TimerTask導(dǎo)致內(nèi)存泄露
??????6.集合中的對(duì)象未清理造成內(nèi)存泄露
??????7.資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露
??????8.屬性動(dòng)畫造成內(nèi)存泄露
??????9.WebView造成內(nèi)存泄露
三掷伙、總結(jié)
歡迎評(píng)論留言蹬叭,文章持續(xù)更新優(yōu)化
一行疏、內(nèi)存泄漏介紹
不少人認(rèn)為JAVA程序弱匪,因?yàn)橛欣厥諜C(jī)制柠逞,應(yīng)該沒有內(nèi)存泄露昧狮。我們已經(jīng)知道了,如果某個(gè)對(duì)象板壮,從根節(jié)點(diǎn)可到達(dá)逗鸣,也就是存在從根節(jié)點(diǎn)到該對(duì)象的引用鏈,那么該對(duì)象是不會(huì)被 GC 回收的绰精。如果說這個(gè)對(duì)象已經(jīng)不會(huì)再被使用到了撒璧,是無用的,我們依然持有他的引用的話笨使,就會(huì)造成內(nèi)存泄漏卿樱,例如 一個(gè)長(zhǎng)期在后臺(tái)運(yùn)行的線程持有 Activity 的引用,這個(gè)時(shí) 候 Activity 執(zhí)行了 onDestroy 方法硫椰,那么這個(gè) Activity 就是從根節(jié)點(diǎn)可到達(dá)并且無用的對(duì)象繁调, 這個(gè) Activity 對(duì)象就是泄漏的對(duì)象,給這個(gè)對(duì)象分配的內(nèi)存將無法被回收靶草。如果我們的java運(yùn)行很久,而這種內(nèi)存泄露不斷的發(fā)生蹄胰,最后就沒內(nèi)存可用了。
Android的一個(gè)應(yīng)用程序的內(nèi)存泄露對(duì)別的應(yīng)用程序影響不大爱致。為了能夠使得Android應(yīng)用程序安全且快速的運(yùn)行烤送,Android的每個(gè)應(yīng)用程序都會(huì)使用一個(gè)專有的Dalvik虛擬機(jī)實(shí)例來運(yùn)行,它是由Zygote服務(wù)進(jìn)程孵化出來的糠悯,也就是說每個(gè)應(yīng)用程序都是在屬于自己的進(jìn)程中運(yùn)行的帮坚。Android為不同類型的進(jìn)程分配了不同的內(nèi)存使用上限,如果程序在運(yùn)行過程中出現(xiàn)了內(nèi)存泄漏的而造成應(yīng)用進(jìn)程使用的內(nèi)存超過了這個(gè)上限互艾,則會(huì)被系統(tǒng)視為內(nèi)存泄漏试和,從而被kill掉,這使得僅僅自己的進(jìn)程被kill掉纫普,而不會(huì)影響其他進(jìn)程(如果是system_process等系統(tǒng)進(jìn)程出問題的話阅悍,則會(huì)引起系統(tǒng)重啟)好渠。
二、常見內(nèi)存泄漏場(chǎng)景
1.單例導(dǎo)致內(nèi)存泄露
單例模式在Android開發(fā)中會(huì)經(jīng)常用到节视,但是如果使用不當(dāng)就會(huì)導(dǎo)致內(nèi)存泄露拳锚。因?yàn)閱卫撵o態(tài)特性使得它的生命周期同應(yīng)用的生命周期一樣長(zhǎng),如果一個(gè)對(duì)象已經(jīng)沒有用處了寻行,但是單例還持有它的引用霍掺,那么在整個(gè)應(yīng)用程序的生命周期它都不能正常被回收,從而導(dǎo)致內(nèi)存泄露拌蜘。
public class AppSettings {
private static volatile AppSettings singleton;
private Context mContext;
private AppSettings(Context context) {
this.mContext = context;
}
public static AppSettings getInstance(Context context) {
if (singleton == null) {
synchronized (AppSettings.class) {
if (singleton == null) {
singleton = new AppSettings(context);
}
}
}
return singleton;
}
}
像上面代碼中這樣的單例杆烁,如果我們?cè)谡{(diào)用getInstance(Context context)方法的時(shí)候傳入的context參數(shù)是Activity、Service等上下文简卧,就會(huì)導(dǎo)致內(nèi)存泄露兔魂。 以Activity為例,當(dāng)我們啟動(dòng)一個(gè)Activity举娩,并調(diào)用getInstance(Context context)方法去獲取AppSettings的單例析校,傳入Activity.this作為context,這樣AppSettings類的單例sInstance就持有了Activity的引用铜涉,當(dāng)我們退出Activity時(shí)勺良,該Activity就沒有用了,但是因?yàn)閟Intance作為靜態(tài)單例(在應(yīng)用程序的整個(gè)生命周期中存在)會(huì)繼續(xù)持有這個(gè)Activity的引用骄噪,導(dǎo)致這個(gè)Activity對(duì)象無法被回收釋放尚困,這就造成了內(nèi)存泄露。
為了避免這樣單例導(dǎo)致內(nèi)存泄露链蕊,我們可以將context參數(shù)改為全局的上下文:
private AppSettings(Context context) {
this.mContext = context.getApplicationContext();
}
2.靜態(tài)變量導(dǎo)致內(nèi)存泄漏
靜態(tài)變量存儲(chǔ)在方法區(qū)事甜,它的生命周期從類加載開始,到整個(gè)進(jìn)程結(jié)束滔韵。一旦靜態(tài)變量初始化后逻谦,它所持有的引用只有等到進(jìn)程結(jié)束才會(huì)釋放。 比如下面這樣的情況陪蜻,在Activity中為了避免重復(fù)的創(chuàng)建info邦马,將sInfo作為靜態(tài)變量:
public class MainActivity2 extends AppCompatActivity {
public static Info sInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sInfo = new Info(this);
}
class Info {
private Context mContext;
public Info(Context context) {
this.mContext = context;
}
}
}
Info作為Activity的靜態(tài)成員,并且持有Activity的引用宴卖,但是sInfo作為靜態(tài)變量滋将,生命周期肯定比Activity長(zhǎng)。所以當(dāng)Activity退出后症昏,sInfo仍然引用了Activity随闽,Activity不能被回收,這就導(dǎo)致了內(nèi)存泄露肝谭。
在Android開發(fā)中掘宪,靜態(tài)持有很多時(shí)候都有可能因?yàn)槠涫褂玫纳芷诓灰恢露鴮?dǎo)致內(nèi)存泄露蛾扇,所以我們?cè)谛陆o態(tài)持有的變量的時(shí)候需要多考慮一下各個(gè)成員之間的引用關(guān)系,并且盡量少地使用靜態(tài)持有的變量魏滚,以避免發(fā)生內(nèi)存泄露镀首。當(dāng)然,我們也可以在適當(dāng)?shù)臅r(shí)候講靜態(tài)量重置為null鼠次,使其不再持有引用蘑斧,這樣也可以避免內(nèi)存泄露。
3.非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄露
非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認(rèn)就會(huì)持有外部類的引用须眷,當(dāng)非靜態(tài)內(nèi)部類對(duì)象的生命周期比外部類對(duì)象的生命周期長(zhǎng)時(shí),就會(huì)導(dǎo)致內(nèi)存泄露沟突。非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄露在Android開發(fā)中有一種典型的場(chǎng)景就是使用Handler花颗,很多開發(fā)者在使用Handler是這樣寫的:
public class MainActivity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
start();
}
private void start() {
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
//doNothing
}
}
};
}
也許有人會(huì)說,mHandler并未作為靜態(tài)變量持有Activity引用惠拭,生命周期可能不會(huì)比Activity長(zhǎng)扩劝,應(yīng)該不一定會(huì)導(dǎo)致內(nèi)存泄露呢,顯然不是這樣的职辅! 熟悉Handler消息機(jī)制的都知道棒呛,mHandler會(huì)作為成員變量保存在發(fā)送的消息msg中,即msg持有mHandler的引用域携,而mHandler是Activity的非靜態(tài)內(nèi)部類實(shí)例簇秒,即mHandler持有Activity的引用,那么我們就可以理解為msg間接持有Activity的引用秀鞭。msg被發(fā)送后先放到消息隊(duì)列MessageQueue中趋观,然后等待Looper的輪詢處理(MessageQueue和Looper都是與線程相關(guān)聯(lián)的,MessageQueue是Looper引用的成員變量锋边,而Looper是保存在ThreadLocal中的)皱坛。那么當(dāng)Activity退出后,msg可能仍然存在于消息對(duì)列MessageQueue中未處理或者正在處理豆巨,那么這樣就會(huì)導(dǎo)致Activity無法被回收剩辟,以致發(fā)生Activity的內(nèi)存泄露。
通常在Android開發(fā)中如果要使用內(nèi)部類往扔,但又要規(guī)避內(nèi)存泄露贩猎,一般都會(huì)采用靜態(tài)內(nèi)部類+弱引用的方式。
MyHandler mHandler;
public static class MyHandler extends Handler {
private WeakReference<Activity> mActivityWeakReference;
public MyHandler(Activity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
mHandler通過弱引用的方式持有Activity萍膛,當(dāng)GC執(zhí)行垃圾回收時(shí)融欧,遇到Activity就會(huì)回收并釋放所占據(jù)的內(nèi)存單元。這樣就不會(huì)發(fā)生內(nèi)存泄露了卦羡。 上面的做法確實(shí)避免了Activity導(dǎo)致的內(nèi)存泄露噪馏,發(fā)送的msg不再已經(jīng)沒有持有Activity的引用了麦到,但是msg還是有可能存在消息隊(duì)列MessageQueue中,所以更好的是在Activity銷毀時(shí)就將mHandler的回調(diào)和發(fā)送的消息給移除掉欠肾。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非靜態(tài)內(nèi)部類造成內(nèi)存泄露還有一種情況就是使用Thread或者AsyncTask瓶颠。要避免內(nèi)存泄露的話還是需要像上面Handler一樣使用靜態(tài)內(nèi)部類+弱應(yīng)用的方式(代碼就不列了,參考上面Hanlder的正確寫法)刺桃。
4.未取消注冊(cè)或回調(diào)導(dǎo)致內(nèi)存泄露
比如我們?cè)贏ctivity中注冊(cè)廣播粹淋,如果在Activity銷毀后不取消注冊(cè),那么這個(gè)剛播會(huì)一直存在系統(tǒng)中瑟慈,同上面所說的非靜態(tài)內(nèi)部類一樣持有Activity引用桃移,導(dǎo)致內(nèi)存泄露。因此注冊(cè)廣播后在Activity銷毀后一定要取消注冊(cè)葛碧。 在注冊(cè)觀察則模式的時(shí)候借杰,如果不及時(shí)取消也會(huì)造成內(nèi)存泄露。比如使用Retrofit+RxJava注冊(cè)網(wǎng)絡(luò)請(qǐng)求的觀察者回調(diào)进泼,同樣作為匿名內(nèi)部類持有外部引用蔗衡,所以需要記得在不用或者銷毀的時(shí)候取消注冊(cè)。
5.Timer和TimerTask導(dǎo)致內(nèi)存泄露
Timer和TimerTask在Android中通常會(huì)被用來做一些計(jì)時(shí)或循環(huán)任務(wù)乳绕,比如實(shí)現(xiàn)無限輪播的ViewPager:
private void stopTimer(){
if(mTimer!=null){
mTimer.cancel();
mTimer.purge();
mTimer = null;
}
if(mTimerTask!=null){
mTimerTask.cancel();
mTimerTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer();
}
當(dāng)我們Activity銷毀的時(shí)绞惦,有可能Timer還在繼續(xù)等待執(zhí)行TimerTask,它持有Activity的引用不能被回收洋措,因此當(dāng)我們Activity銷毀的時(shí)候要立即cancel掉Timer和TimerTask济蝉,以避免發(fā)生內(nèi)存泄漏。
6.集合中的對(duì)象未清理造成內(nèi)存泄露
這個(gè)比較好理解菠发,如果一個(gè)對(duì)象放入到ArrayList堆生、HashMap等集合中,這個(gè)集合就會(huì)持有該對(duì)象的引用雷酪。當(dāng)我們不再需要這個(gè)對(duì)象時(shí)淑仆,也并沒有將它從集合中移除,這樣只要集合還在使用(而此對(duì)象已經(jīng)無用了)哥力,這個(gè)對(duì)象就造成了內(nèi)存泄露蔗怠。并且如果集合被靜態(tài)引用的話,集合里面那些沒有用的對(duì)象更會(huì)造成內(nèi)存泄露了吩跋。所以在使用集合時(shí)要及時(shí)將不用的對(duì)象從集合remove寞射,或者clear集合,以避免內(nèi)存泄漏锌钮。
7.資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露
在使用IO桥温、File流或者Sqlite、Cursor等資源時(shí)要及時(shí)關(guān)閉。這些資源在進(jìn)行讀寫操作時(shí)通常都使用了緩沖赚楚,如果不及時(shí)關(guān)閉愕鼓,這些緩沖對(duì)象就會(huì)一直被占用而得不到釋放扰她,以致發(fā)生內(nèi)存泄露。因此我們?cè)诓恍枰褂盟鼈兊臅r(shí)候就及時(shí)關(guān)閉投剥,以便緩沖能及時(shí)得到釋放沈善,從而避免內(nèi)存泄露伯诬。
8.屬性動(dòng)畫造成內(nèi)存泄露
動(dòng)畫同樣是一個(gè)耗時(shí)任務(wù)澳腹,比如在Activity中啟動(dòng)了屬性動(dòng)畫(ObjectAnimator)织盼,但是在銷毀的時(shí)候,沒有調(diào)用cancle方法酱塔,雖然我們看不到動(dòng)畫了沥邻,但是這個(gè)動(dòng)畫依然會(huì)不斷地播放下去,動(dòng)畫引用所在的控件羊娃,所在的控件引用Activity唐全,這就造成Activity無法正常釋放。因此同樣要在Activity銷毀的時(shí)候cancel掉屬性動(dòng)畫迁沫,避免發(fā)生內(nèi)存泄漏。
9.WebView造成內(nèi)存泄露
關(guān)于WebView的內(nèi)存泄露捌蚊,因?yàn)閃ebView在加載網(wǎng)頁后會(huì)長(zhǎng)期占用內(nèi)存而不能被釋放集畅,因此我們?cè)贏ctivity銷毀后要調(diào)用它的destory()方法來銷毀它以釋放內(nèi)存。另外在查閱WebView內(nèi)存泄露相關(guān)資料時(shí)看到這種情況: Webview下面的Callback持有Activity引用缅糟,造成Webview內(nèi)存無法釋放挺智,即使是調(diào)用了Webview.destory()等方法都無法解決問題(Android5.1之后)。 最終的解決方案是:在銷毀WebView之前需要先將WebView從父容器中移除窗宦,然后再銷毀WebView赦颇。
三、總結(jié)
對(duì)于生命周期比Activity長(zhǎng)的對(duì)象(單例)赴涵,要避免直接引用Activity的context媒怯,可以考慮使用ApplicationContext,靜態(tài)變量不使用時(shí)及時(shí)置空髓窜;
Handler持有的引用最好使用弱引用扇苞,在Activity被釋放的時(shí)候要記得清空Message,取消Handler對(duì)象的Runnable寄纵;
非靜態(tài)內(nèi)部類鳖敷、非靜態(tài)匿名內(nèi)部類會(huì)自動(dòng)持有外部類的引用,為避免內(nèi)存泄露程拭,可以考慮把內(nèi)部類聲明為靜態(tài)的定踱;
廣播接收器、EventBus等的使用過程中恃鞋,注冊(cè)/反注冊(cè)應(yīng)該成對(duì)使用崖媚,但凡有注冊(cè)的都應(yīng)該有反注冊(cè)亦歉;
不再使用的資源對(duì)象Cursor、File至扰、Bitmap等要記住正確關(guān)閉鳍徽;
集合里面的東西有加入就應(yīng)該對(duì)應(yīng)有相應(yīng)的刪除。
7.屬性動(dòng)畫及時(shí)取消敢课,注意webview內(nèi)存泄漏問題阶祭。