科普:內(nèi)存泄漏與內(nèi)存溢出

最近項(xiàng)目中頻繁出現(xiàn)OOM的問(wèn)題,各種路徑測(cè)試、內(nèi)存走向分析、各種邏輯推理才最終定位到問(wèn)題。在這過(guò)程中和組內(nèi)的同學(xué)討論的時(shí)候發(fā)現(xiàn)有的同學(xué)對(duì)內(nèi)存泄漏和內(nèi)存溢出的概念理解不到位,導(dǎo)致溝通過(guò)程比較尷尬。很多同學(xué)對(duì)這兩個(gè)概念理解不夠透徹,在項(xiàng)目中頻繁寫出內(nèi)存泄漏的低級(jí)代碼出來(lái)。結(jié)合自己的理解我寫一篇文章理解下這兩個(gè)概念。

內(nèi)存泄漏

內(nèi)存泄漏是指那些本應(yīng)該回收(不再使用)的內(nèi)存對(duì)象無(wú)法被系統(tǒng)回收的現(xiàn)象。在c++中需要程序猿手動(dòng)釋放內(nèi)存對(duì)象,所以在C++中更容易存在內(nèi)存泄漏。java引入了自動(dòng)回收機(jī)制,使得在C++中令人頭疼的內(nèi)存問(wèn)題得到了有效的改善,但這并不意味著java程序員不關(guān)注內(nèi)存,因?yàn)槔厥諜C(jī)制不能完全保證內(nèi)存對(duì)象在該釋放的地方釋放,現(xiàn)代java虛擬機(jī)中普遍使用根集算法去計(jì)算對(duì)象的引用可達(dá)性,不可達(dá)的才能回收,例如下圖中的無(wú)用對(duì)象被有用對(duì)象引用著,導(dǎo)致無(wú)用對(duì)象引用一直可達(dá),系統(tǒng)回收器不敢冒然回收,從而造成內(nèi)存泄漏。

內(nèi)存泄漏.png

內(nèi)存溢出

系統(tǒng)在為某段執(zhí)行指令(程序)分配內(nèi)存的時(shí)候,發(fā)現(xiàn)內(nèi)存不足,拋出錯(cuò)誤,這叫做內(nèi)存溢出。

內(nèi)存溢出

二者關(guān)系

手機(jī)設(shè)備的內(nèi)存空間是有限的,為每個(gè)應(yīng)用所分配到的內(nèi)存空間更是有限的,當(dāng)內(nèi)存泄漏對(duì)象越來(lái)越多,可調(diào)配內(nèi)存空間就越小,App應(yīng)用性能越差,當(dāng)可調(diào)配的內(nèi)存空間不夠創(chuàng)建新對(duì)象時(shí)就會(huì)引起OOM。

內(nèi)存泄漏與溢出.png

內(nèi)存泄漏經(jīng)典模型

靜態(tài)變量

靜態(tài)變量的生命周期是最長(zhǎng)的,和應(yīng)用程序的生命周期一樣,當(dāng)一個(gè)大對(duì)象被一個(gè)類的靜態(tài)變量引用時(shí),這個(gè)對(duì)象就無(wú)法被系統(tǒng)回收,在應(yīng)用的整個(gè)生命周期中占用內(nèi)存。常見(jiàn)于工具類,一般中存在大量的工具類,很多同學(xué)圖方便直接或間接使用靜態(tài)變量引用一個(gè)上下文對(duì)象的。

 public class NotificationUtil { 
 //靜態(tài)變量,notificationManager泄漏
  private static  NotificationManager notificationManager;   
  public static void notification(Context context, Class<?> cls, Message msg) {   
     if (notificationManager == null){    
        notificationManager = (NotificationManager)       
        context.getSystemService(context.NOTIFICATION_SERVICE);   
      }
    //....
 }
}

NotificationManager 對(duì)象泄漏了,同時(shí)因?yàn)?NotificationManager 對(duì)象中有一個(gè)上下文對(duì)象mContext變量,又回引起啟動(dòng)這個(gè)方法的Context對(duì)象泄漏。

規(guī)避:

對(duì)于工具類,如非頻繁使用的對(duì)象,盡量不要使用 static 變量去引用,可以在方法執(zhí)行時(shí)候再創(chuàng)建,作為局部變量使用;如需要頻繁使用,為了提高方法執(zhí)行效率,對(duì)于上面這種情況可以把Context 參數(shù)限制為Application 級(jí)別的上下文,避免調(diào)用方傳遞Activity級(jí)別的上下文,造成Activity泄漏。

public class NotificationUtil { 
 //靜態(tài)變量
  private static  NotificationManager notificationManager;   
  public static void notification(Application context, Class<?> cls, Message msg) {   
    //context限制為Application級(jí)別
     if (notificationManager == null){    
        notificationManager = (NotificationManager)       
        context.getSystemService(context.NOTIFICATION_SERVICE);   
      }
    //.....
 }
}

單例模式

單例模式其實(shí)也是靜態(tài)變量的一種,單例的生命周期和靜態(tài)變量時(shí)一樣的,如果這個(gè)單例中持有一個(gè)大對(duì)象,就會(huì)引起這個(gè)大對(duì)象泄漏。

private static WebViewClient instance;
public static WebViewClient getInstance(Context context) {   
 if (instance == null) {  
    //mContext有泄漏風(fēng)險(xiǎn)
    instance = new WebViewClient(context, jsToJava); 
  }   
 return instance;
} 

例如這里的mContext,如果是個(gè)Activity的話,會(huì)被instance長(zhǎng)期引用著的。

規(guī)避:

和靜態(tài)變量一樣的道理,盡量使用Application級(jí)別的上下文代替Activity級(jí)別的上下文。

private static WebViewClient instance;
public static WebViewClient getInstance(Application context) {   
  //context限制為Application級(jí)別的
 if (instance == null) {  
    instance = new WebViewClient(context, jsToJava); 
  }   
 return instance;
}

內(nèi)部類

由于內(nèi)部類的存在需要依賴它的外部類,由于某些原因?qū)е聝?nèi)部類被引用會(huì)無(wú)法退出,引起外部類無(wú)法回收,這是使用最多也是最容易被用出內(nèi)存泄漏的了。

內(nèi)部類循環(huán).png

內(nèi)部類循環(huán)或者阻塞,下面就有一個(gè)奇葩的代碼,一個(gè)研究生寫的:

import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
public class CheckEditText extends EditText{
 public CheckEditText(Context context,AttributeSet attrs){
  super(context,attrs);
  new Thread(){
   @Override
   public void run(){
      while(true) {
         String content = this.getText().toString();
         if(content.length() > 12){
            //字符長(zhǎng)度不能大于12
            //...
         }
     }
   }
  }.start();
 }
}

然后所有使用這個(gè)CheckEditText的Activity都泄漏了。

內(nèi)部類被其他對(duì)象持有.png

這種模式最常見(jiàn)的就是Handler的使用了,一般項(xiàng)目中很多內(nèi)存泄漏就是這種模型:

//內(nèi)部類的Handler,有內(nèi)存泄漏風(fēng)險(xiǎn)
Handler handler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        int what = msg.what;
        switch (what) {
            case SHOW:
                progressDialog = ProgressDialog.show(DetailActivity.this, null, "圖片上傳中...");
                break;
            case DISMISS:
                if (progressDialog != null && progressDialog.isShowing()) {
                    progressDialog.dismiss();
                }
                break;
            case UPLOADPIC:
                String base64ImageString = (String) msg.obj;
                webView.loadUrl("javascript:fileChooserCallback(" + "'" + base64ImageString
                        + "'" + ")");
                break;

            default:
                break;
        }

    }

};

這是DetailActivity中的一個(gè)Handler內(nèi)部類,這會(huì)導(dǎo)致DetailActivity泄漏,因?yàn)镠andler其實(shí)是被一個(gè)消息隊(duì)列引用著。

規(guī)避:

對(duì)于那些可能長(zhǎng)時(shí)間執(zhí)行、阻塞或者被外部引用的內(nèi)部盡量使用靜態(tài)內(nèi)部類代替。靜態(tài)內(nèi)部類對(duì)象的存在不依附外部類,這樣可以避開內(nèi)部類對(duì)外部類的隱性引用,然后使用弱引用持有外部類對(duì)象。

 static class MyHandler extends  Handler {
      //靜態(tài)內(nèi)部類代替,并使用若引用持有DetailActivity
    private WeakReference<DetailActivity> weakReference;
    public MyHandler(DetailActivity activity) {
        this.weakReference = new WeakReference(activity);
    }
    public void handleMessage(android.os.Message msg) {
        int what = msg.what;
        DetailActivity activity = weakReference.get();
        if (activity == null){
            return;
        }
        switch (what) {
            case SHOW:
                activity.progressDialog = ProgressDialog.show(activity, null, "圖片上傳中...");
                break;
            case DISMISS:
                if (activity.progressDialog != null && activity.progressDialog.isShowing()) {
                    activity.progressDialog.dismiss();
                }
                break;
            case UPLOADPIC:
                String base64ImageString = (String) msg.obj;
                activity.webView.loadUrl("javascript:fileChooserCallback(" + "'" + base64ImageString
                        + "'" + ")");
                break;
            default:
                break;
        }

    }
};
MyHandler handler = new MyHandler(this);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    宇宙只有巴掌大閱讀 2,494評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    _痞子閱讀 1,703評(píng)論 0 8
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講,...
    DreamFish閱讀 875評(píng)論 0 5
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏...
    apkcore閱讀 1,310評(píng)論 2 7
  • 人不能沒(méi)有良心,靜下心來(lái)想想我的朋友,自從你離開家門口到現(xiàn)在為止,你只要稍加努力,你都可以每個(gè)月多掙五十,一百、乃...
    湯程耀閱讀 773評(píng)論 0 0

友情鏈接更多精彩內(nèi)容