1. 概述
Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒(méi)有使用價(jià)值了酌伊,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無(wú)法被GC回收放棒。無(wú)用的對(duì)象占據(jù)著內(nèi)存空間死讹,使得實(shí)際可使用內(nèi)存變小推励,形象地說(shuō)法就是內(nèi)存泄漏了悉默。
2. 常見(jiàn)泄露類(lèi)型
2.1. 集合類(lèi)泄露
如果集合類(lèi)僅僅有添加元素城豁,而沒(méi)有相應(yīng)的刪除機(jī)制,會(huì)導(dǎo)致內(nèi)存被占用抄课。當(dāng)將集合中元素置空唱星,但是集合因?yàn)槌钟袑?duì)元素的引用雳旅,導(dǎo)致內(nèi)存回收不,而發(fā)生內(nèi)存泄露间聊。解決方法是攒盈,可先刪除元素然后置空,或者直接將集合置空哎榴。
Android 中常見(jiàn)的集合類(lèi)內(nèi)存泄露有ValueAnimator調(diào)用addUpdateListener型豁,EditText調(diào)用addTextChangedListener()而未注銷(xiāo)監(jiān)聽(tīng)導(dǎo)致內(nèi)存泄露,他們代碼如下
//ValueAnimator
public void addUpdateListener(AnimatorUpdateListener listener) {
if (mUpdateListeners == null) {
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
}
mUpdateListeners.add(listener);
}
//EditText
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
2.2 靜態(tài)變量引起的內(nèi)存泄漏
在java中靜態(tài)變量的生命周期是在類(lèi)加載時(shí)開(kāi)始尚蝌,類(lèi)卸載時(shí)結(jié)束迎变。換句話(huà)說(shuō),在android中其生命周期是在進(jìn)程啟動(dòng)時(shí)開(kāi)始飘言,進(jìn)程死亡時(shí)結(jié)束衣形。所以在程序的運(yùn)行期間,如果進(jìn)程沒(méi)有被殺死姿鸿,靜態(tài)變量就會(huì)一直存在谆吴,不會(huì)被回收掉。如果靜態(tài)變量強(qiáng)引用了某個(gè)Activity中變量苛预,那么這個(gè)Activity就同樣也不會(huì)被釋放,即便是該Activity執(zhí)行了onDestroy(不要將執(zhí)行onDestroy和被回收劃等號(hào))句狼。這類(lèi)問(wèn)題的解決方案為:
- 尋找與該靜態(tài)變量生命周期差不多的替代對(duì)象。
- 若找不到碟渺,將強(qiáng)引用方式改成弱引用
2.2.1 單例引起的Context內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長(zhǎng)鲜锚,所以如果使用不恰當(dāng)?shù)脑?huà),很容易造成內(nèi)存泄漏苫拍。如果單例中引用activity類(lèi)型Context而非ApplicationContext芜繁,會(huì)導(dǎo)致ondestroy后不能被回收
public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}
可以讓傳入的context 轉(zhuǎn)化為ApplicationContext。
2.2.2 靜態(tài)Activity和View绒极,drawable
靜態(tài)變量Activity和View會(huì)導(dǎo)致內(nèi)存泄漏骏令,在下面這段代碼中對(duì)Activity的Context和TextView設(shè)置為靜態(tài)對(duì)象,從而產(chǎn)生內(nèi)存泄漏垄提。
public class MainActivity extends AppCompatActivity {
private static Context context;
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(this);
}
}
靜態(tài)變量drawable 也類(lèi)似榔袋,但android 4.0的Drawable.Java對(duì)setCallback的實(shí)現(xiàn)進(jìn)行了軟引用,避免了內(nèi)存泄露:
public final void setCallback(Callback cb){
mCallback = newWeakReference<Callback> (cb);
}
2.2.3 非靜態(tài)內(nèi)部類(lèi)創(chuàng)建靜態(tài)實(shí)例
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
因?yàn)榉庆o態(tài)內(nèi)部類(lèi)默認(rèn)會(huì)持有外部類(lèi)的引用铡俐,而該非靜態(tài)內(nèi)部類(lèi)TestResource又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例怔锌,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用笋婿,導(dǎo)致Activity的內(nèi)存資源不能正撤枋睿回收。
2.3. 線(xiàn)程造成內(nèi)存泄露
android開(kāi)發(fā)經(jīng)常會(huì)繼承實(shí)現(xiàn)Activity/Fragment/View,此時(shí)如果你使用了匿名類(lèi)/非靜態(tài)內(nèi)部類(lèi)锅知,并被異步線(xiàn)程持有了播急,那要小心了,如果沒(méi)有任何措施這樣一定會(huì)導(dǎo)致泄露
2.3.1 多線(xiàn)程
如果多線(xiàn)程中含有外部activity的引用售睹,當(dāng)activity銷(xiāo)毀后桩警,而多線(xiàn)程任務(wù)還未執(zhí)行完,因?yàn)榫€(xiàn)程持有對(duì)activity的引用而導(dǎo)致activity不能被回收掉
2.3.2 內(nèi)部線(xiàn)程造成內(nèi)存泄露
public class LeakActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
leakFun();
}
private void leakFun(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
可以將leakFun()加上static 改成靜態(tài)方法昌妹,讓匿名內(nèi)部類(lèi)不會(huì)持有LeakActivity.this應(yīng)用捶枢。
2.3.1 handler
public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 刷新數(shù)據(jù)
break;
default:
break;
}
};
};
private void fetchData() {
//獲取數(shù)據(jù)
mHandler.sendEmptyMessage(0);
}
}
mHandler 為匿名內(nèi)部類(lèi)實(shí)例,會(huì)引用外圍對(duì)象LeakAty.this,如果該Handler在A(yíng)ctivity退出時(shí)依然還有消息需要處理捺宗,那么這個(gè)Activity就不會(huì)被回收柱蟀。
如果當(dāng)Handler為非靜態(tài)內(nèi)部類(lèi)也會(huì)導(dǎo)致內(nèi)存泄露,因?yàn)榉庆o態(tài)內(nèi)部類(lèi)也會(huì)持有外部類(lèi)的引用蚜厉,萬(wàn)一 Handler 發(fā)送的 Message 尚未被處理长已,則該 Message 及發(fā)送它的 Handler 對(duì)象將被線(xiàn)程 MessageQueue 一直持有。
2.3.2 AsyncTask
AsyncTask 情況類(lèi)似handler昼牛,都有由于非靜態(tài)內(nèi)部類(lèi)术瓮,或者匿名內(nèi)部類(lèi)持有activity的引用,當(dāng)activity退出時(shí)任務(wù)還未完成繼續(xù)持有對(duì)activity的引用贰健,導(dǎo)致activity不能回收胞四。
2.4. 動(dòng)畫(huà)導(dǎo)致的內(nèi)存泄露
Android在動(dòng)畫(huà)使用過(guò)程如果不注意也會(huì)經(jīng)常導(dǎo)致內(nèi)存泄露。
例如屬性動(dòng)畫(huà)onDestroy中未停止動(dòng)畫(huà)伶椿,這時(shí)候Activity會(huì)被View所持有辜伟,從而導(dǎo)致Activity無(wú)法被釋放。解決方法onDestroy去去調(diào)用objectAnimator.cancel()來(lái)停止動(dòng)畫(huà)脊另。
public class LeakActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
textView = (TextView)findViewById(R.id.text_view);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
}
2.5 資源未關(guān)閉或注銷(xiāo)引用內(nèi)存泄露
常見(jiàn)由于資源未關(guān)閉/注銷(xiāo)導(dǎo)致內(nèi)存泄露有
- BraodcastReceiver
- ContentObserver
- File
- 游標(biāo) Cursor
- Stream
- Bitmap
...
這些資源應(yīng)該打開(kāi)导狡,用完后馬上關(guān)閉,或者在A(yíng)ctivity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo)偎痛,否則這些資源將不會(huì)被回收旱捧,造成內(nèi)存泄漏。
2.6. 不良代碼
- 構(gòu)造 Adapter 時(shí)踩麦,沒(méi)有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView
- EventBus枚赡,RxJava等一些第三開(kāi)源框架的使用,若是在A(yíng)ctivity銷(xiāo)毀之前沒(méi)有進(jìn)行解除訂閱將會(huì)導(dǎo)致內(nèi)存泄漏谓谦。
···
3. 內(nèi)存泄露解決措施
3.1. 工具方面:
- LeakCanary 檢測(cè) Android 的內(nèi)存泄漏
- Android Studio 的Monitor監(jiān)測(cè)內(nèi)存使用情況
- MAT分析heap的總內(nèi)存占用大小來(lái)初步判斷是否存在泄露
3.2 代碼方面
- 引入弱引用
- 使用合適的Context贫橙,盡量使用ApplicationContext
- 在 Activity 的 Destroy 時(shí)或者 Stop 時(shí)應(yīng)該移除消息隊(duì)列 MessageQueue 中的消息。
- 盡量避免使用 static成員變量
- 使用的資源及時(shí)關(guān)閉反粥、注銷(xiāo)
- 避免非靜態(tài)內(nèi)部類(lèi)料皇,可改成靜態(tài)內(nèi)部類(lèi)
- 避免匿名內(nèi)部類(lèi)