1. 程式人生 > >記憶體洩漏與記憶體溢位的區別

記憶體洩漏與記憶體溢位的區別

記憶體洩漏

記憶體洩漏是指那些本應該回收(不再使用)的記憶體物件無法被系統回收的現象。在c++中需要程式猿手動釋放記憶體物件,所以在C++中更容易存在記憶體洩漏。java引入了自動回收機制,使得在C++中令人頭疼的記憶體問題得到了有效的改善,但這並不意味著java程式設計師不關注記憶體,因為垃圾回收機制不能完全保證記憶體物件在該釋放的地方釋放,現代java虛擬機器中普遍使用根集演算法去計算物件的引用可達性,不可達的才能回收,例如下圖中的無用物件被有用物件引用著,導致無用物件引用一直可達,系統回收器不敢冒然回收,從而造成記憶體洩漏。

記憶體溢位

系統在為某段執行指令(程式)分配記憶體的時候,發現記憶體不足,丟擲錯誤,這叫做記憶體溢位。

二者關係

手機裝置的記憶體空間是有限的,為每個應用所分配到的記憶體空間更是有限的,當記憶體洩漏物件越來越多,可調配記憶體空間就越小,App應用效能越差,當可調配的記憶體空間不夠建立新物件時就會引起OOM。

記憶體洩漏經典模型

靜態變數

靜態變數的生命週期是最長的,和應用程式的生命週期一樣,當一個大物件被一個類的靜態變數引用時,這個物件就無法被系統回收,在應用的整個生命週期中佔用記憶體。常見於工具類,一般中存在大量的工具類,很多同學圖方便直接或間接使用靜態變數引用一個上下文物件的。

public class NotificationUtil { 
     //靜態變數,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 物件洩漏了,同時因為 NotificationManager 物件中有一個上下文物件mContext變數,又回引起啟動這個方法的Context物件洩漏。

規避:

對於工具類,如非頻繁使用的物件,儘量不要使用 static 變數去引用,可以在方法執行時候再建立,作為區域性變數使用;如需要頻繁使用,為了提高方法執行效率,對於上面這種情況可以把Context 引數限制為Application 級別的上下文,避免呼叫方傳遞Activity級別的上下文,造成Activity洩漏。
 
     

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

單例模式

單例模式其實也是靜態變數的一種,單例的生命週期和靜態變數時一樣的,如果這個單例中持有一個大物件,就會引起這個大物件洩漏。
   
     

private static WebViewClient instance;
    public static WebViewClient getInstance(Context context) {   
     if (instance == null) {  
        //mContext有洩漏風險
        instance = new WebViewClient(context, jsToJava); 
      }   
     return instance;
    } 

例如這裡的mContext,如果是個Activity的話,會被instance長期引用著的。

規避:

和靜態變數一樣的道理,儘量使用Application級別的上下文代替Activity級別的上下文。
    
    private static WebViewClient instance;
    public static WebViewClient getInstance(Application context) {   
      //context限制為Application級別的
     if (instance == null) {  
        instance = new WebViewClient(context, jsToJava); 
      }   
     return instance;
    }

內部類

由於內部類的存在需要依賴它的外部類,由於某些原因導致內部類被引用會無法退出,引起外部類無法回收,這是使用最多也是最容易被用出記憶體洩漏的了。


內部類迴圈或者阻塞,下面就有一個奇葩的程式碼,一個研究生寫的:
  
    

 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){
                //字元長度不能大於12
                //...
             }
         }
       }
      }.start();
     }
    }

然後所有使用這個CheckEditText的Activity都洩漏了。

這種模式最常見的就是Handler的使用了,一般專案中很多記憶體洩漏就是這種模型:

//內部類的Handler,有記憶體洩漏風險
    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中的一個Handler內部類,這會導致DetailActivity洩漏,因為Handler其實是被一個訊息佇列引用著。

規避:

對於那些可能長時間執行、阻塞或者被外部引用的內部儘量使用靜態內部類代替。靜態內部類物件的存在不依附外部類,這樣可以避開內部類對外部類的隱性引用,然後使用弱引用持有外部類物件。

 static class MyHandler extends  Handler {
          //靜態內部類代替,並使用若引用持有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);

尊重作者版權,如果本人有不對的地方,請聯絡本人,即可刪除

原文地址:http://www.apkbus.com/?705730