內存泄露



定義

內存泄露是指無用對象(再也不使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而形成的內存空間的浪費稱爲內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重會提示你Out of memoryjava

緣由

長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是java中內存泄露的發生場景git

危害

只有一個,那就是虛擬機佔用內存太高,致使OOM(內存溢出),程序出錯。對於Android應用來講,就是你的用戶打開一個Activity,使用完以後關閉它,內存泄露;又打開,又關閉,又泄露;幾回以後,程序佔用內存超過系統限制FCgithub

Android手機給應用分配的內存一般是8兆左右,若是處理內存處理不當很容易形成OutOfMemoryError數據庫

OutOfMemoryError主要由如下幾種狀況形成:緩存

  1. 數據庫Cursor沒關。
    當咱們操做完數據庫後,必定要調用close()釋放資源。ide

  2. 構造Adapter沒有使用緩存ContentView函數

    @Override  public View getView(int position, View convertView, ViewGroup parent) {  
        ViewHolder vHolder = null;  
        //若是convertView對象爲空則建立新對象,不爲空則複用  
        if (convertView == null) {  
            convertView = inflater.inflate(..., null);  
            // 建立 ViewHodler 對象  
            vHolder = new ViewHolder();  
            vHolder.img= (ImageView) convertView.findViewById(...);  
            vHolder.tv= (TextView) convertView  
                    .findViewById(...);  
            // 將ViewHodler保存到Tag中  
            convertView.setTag(vHolder);  
        } else {  
            //當convertView不爲空時,經過getTag()獲得View  
            vHolder = (ViewHolder) convertView.getTag();  
        }  
        // 給對象賦值,修改顯示的值  
        vHolder.img.setImageBitmap(...);  
        vHolder.tv.setText(...);  
        return convertView;  }  static class ViewHolder {  
        TextView tv;  
        ImageView img;  }
  3. 未取消註冊廣播接收者
    registerReceiver()unregisterReceiver()要成對出現,一般須要在ActivityonDestory()方法去取消註冊廣播接收者。post

  4. IO流未關閉 注意用完後及時關閉ui

  5. Bitmap使用後未調用recycle()。this

  6. Context泄漏。 這是一個很隱晦的OutOfMemoryError的狀況。先看一個Android官網提供的例子:

    private static Drawable sBackground;  @Override  protected void onCreate(Bundle state) {  
        super.onCreate(state);  
        TextView label = new TextView(this);  
        label.setText("Leaks are bad");  
        if (sBackground == null) {  
            sBackground = getDrawable(R.drawable.large_bitmap);  
        }  
        label.setBackgroundDrawable(sBackground);  
        setContentView(label);  }

    這段代碼效率很快,但同時又是極其錯誤的:
    Drawable擁有一個TextView的引用,而TextView又擁有Activity(Context類型)的引用,Drawable擁有了更多的對象引用。即便Activity被銷燬,內存仍然不會被釋放。
    另外,對Context的引用超過它自己的生命週期,也會致使Context泄漏。因此儘可能使用Application這種Context類型。因此若是打算保存一個長時間的對象時,要使用getApplicationContext()。 最近遇到一種狀況引發了Context泄漏,就是在Activity銷燬時,裏面有其餘線程沒有停。

    總結一下避免Context泄漏應該注意的問題:

    • 使用getApplicationContext()類型。

    • 注意對Context的引用不要超過它自己的生命週期。

    • 慎重的使用static關鍵字。

    • Context裏若是有線程,必定要在onDestroy()裏及時停掉。

  7. 線程 線程也是形成內存泄露的一個重要的源頭。線程產生內存泄露的主要緣由在於線程生命週期的不可控。咱們來考慮下面一段代碼。

    public class MyActivity extends Activity {     
        @Override     
        public void onCreate(Bundle savedInstanceState) {         
            super.onCreate(savedInstanceState);         
            setContentView(R.layout.main);         
            new MyThread().start();     
        }       
        private class MyThread extends Thread{         
        @Override         
            public void run() {             
            super.run();             
            //耗時的操做       
            }     
        } }

    假設MyThreadrun函數是一個很費時的操做,當調用finish的時候Activity會銷燬掉嗎?
    事實上因爲咱們的線程是Activity的內部類,因此MyThread中保存了Activity的一個引用,當MyThreadrun函數沒有結束時,MyThread是不會被銷燬的,所以它所引用的老的Activity也不會被銷燬,所以就出現了內存泄露的問題。

  8. Handler的使用,在Activity退出的時候注意移除(尤爲是循環的時候)

    public class ThreadDemo extends Activity {  
        private static final String TAG = "ThreadDemo";  
        private int count = 0;  
        private Handler mHandler =  new Handler();  
    
        private Runnable mRunnable = new Runnable() {  
    
            public void run() {  
                //爲了方便 查看,咱們用Log打印出來   
                Log.e(TAG, Thread.currentThread().getName() + " " +count);    
                //每2秒執行一次   
                mHandler.postDelayed(mRunnable, 2000);  
            }   
        };  
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);   
            //經過Handler啓動線程   
            mHandler.post(mRunnable);  
        } }//因此咱們在應用退出時,要將線程銷燬,咱們只要在Activity中的,onDestory()方法處理一下就OK了,以下代碼所示:@Override  
      protected void onDestroy() {  
        mHandler.removeCallbacks(mRunnable);  
        super.onDestroy();  
      }
相關文章
相關標籤/搜索