內(nèi)存泄漏的原因及案例

1.單例導致

由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長,所以如果使用不恰當?shù)脑挘苋菀自斐蓛?nèi)存泄漏。比如下面一個典型的例子:

public class AppManager {
     private static AppManager instance;
     private Context context;
     private AppManager(Context context) {
           this.context = context;
     }
     public static AppManager getInstance(Context context) {
          if (instance != null) {
                instance = new AppManager(context);
          }
          return instance;
     }
}

如果此時傳入的是 Activity 的 Context,當這個 Context 所對應(yīng)的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應(yīng)用程序的生命周期,所以當前 Activity 退出時它的內(nèi)存并不會被回收,這就造成泄漏了,可以換成以下寫法

public class AppManager {
     private static AppManager instance;
     private Context context;
     private AppManager(Context context) {
           this.context = context.getApplicationContext();// 使用Application 的context
     }
     public static AppManager getInstance(Context context) {
          if (instance != null) {
                instance = new AppManager(context);
          }
          return instance;
    }
}

有時候在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復創(chuàng)建,不過這種寫法卻會造成內(nèi)存泄漏,因為非靜態(tài)內(nèi)部類默認會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實例,該實例的生命周期和應(yīng)用的一樣長,這就導致了該靜態(tài)實例一直會持有該Activity的引用,導致Activity的內(nèi)存資源不能正?;厥?/p>

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īng)該是

將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。

2 非靜態(tài)內(nèi)部類導致

原因
1非靜態(tài)內(nèi)部類對外部類會存在一個隱式引用

class Out extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     launch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                outmethod();
            }
        });}
void outmethod(){
}
}
內(nèi)部類能調(diào)用外部類的方法是因為持有外部類的引用

2非靜態(tài)內(nèi)部類中存在異步任務(wù),可能會導致其對應(yīng)的外部類內(nèi)存資源無法正常釋放

class Out extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     launch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.getData();
            }
        });}
void outmethod(){
}
}
這里面有網(wǎng)絡(luò)請求的耗時任務(wù)

解決方案:

解決思路
1去除隱式引用(通過靜態(tài)內(nèi)部類來去除隱式引用)
2手動管理對象引用(修改靜態(tài)內(nèi)部類的構(gòu)造方式,手動引入其外部類引用)
3當內(nèi)存不可用時,不執(zhí)行不可控代碼(Android可以結(jié)合智能指針,WeakReference包裹外部類實例)

class Out extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
     launch.setOnClickListener(new listener(Out.this));
}
     void outmethod(){}
 static  class listener implements View.OnClickListener{
        private WeakReference<Out> weakReference;
        public listener(Out out) {
            this.weakReference=new WeakReference<Out>(out);
        }
        

        @Override
          public void onClick(View view) {
            weakReference.get().outmethod();

          }
      }
}

android中的幾類引用
StrongReference強引用可以直接訪問目標對象,強引用所關(guān)聯(lián)的對象,在任何時候都不會被內(nèi)存回收,JVM寧肯拋出OOM異常,也不會對其進行回收,所以,在通常的內(nèi)存泄漏中,大多都有強引用的身影
(SoftReference)
軟引用是除了硬引用之外最強的一種引用,軟引用和硬引用的不同點在于,軟引用是可被回收的;回收機制是:當內(nèi)存充足的時候,在GC時,不會去回收當前的軟引用,當內(nèi)存臨近閾值或不足的時候,在GC時,發(fā)現(xiàn)某一對象的引用只具有軟引用當前軟引用就會被回收。
(WeakReference)
弱引用是比軟引用和硬引用更弱的一種引用,在GC時,不論內(nèi)存是否充足,發(fā)現(xiàn)某一對象的引用只具有弱引用當前弱引用就會被回收。
(PhantomReference)
虛引用不能保證其保存對象生命周期,其保存對象若只有虛引用,則其有效期完全隨機于GC的回收,在任何一個不確定的時間內(nèi),都可能會被回收;而虛引用與其他幾者的引用不同在于,在使用PhantomReference,必須要和Reference聯(lián)合使用。

引用使用方式

ReferenceQueue queue = new ReferenceQueue();
WeakReference weakReference = new WeakReference(this,queue);
SoftReference softReference = new SoftReference(this,queue);
PhantomReference phantomReference = new PhantomReference(this,queue);

那么這個ReferenceQueue有什么用呢?

引用對象本身,也是一個強引用,其除了具有保存一個對象本身特有的引用屬性之外,引用對象本身也具有java對象的一般性,那么在其本身保存的對象被回收之后,引用對象本身也就沒有了實用性質(zhì),需要一個適當?shù)那謇頇C制,來清理這些對象,避免大量這些引用對象而帶來的內(nèi)存泄漏;這時候,就可以用到ReferenceQueue。

當引用(SoftReference/WeakReference/PhantomReference)中保存的的對象,被GC回收時,引用本身的這個對象會被加入到ReferenceQueue中,那么,也就是說,ReferenceQueue中保存的對象是Reference,并且是失去了其保存的對象的Reference。這個時候我們可以通過調(diào)用ReferenceQueue中提供的poll()這個API來獲取隊列中的對象,當隊列中不存在對象的時候,返回的會是null,當存在或存在多個的時候,都是返回最前面的一個Reference對象,這個時候我們就需要將這個對象進行清除,讓相應(yīng)的內(nèi)存可以被釋放掉。

Reference ref = null;
while ((ref = queue.poll()) != null) {
// 清除ref
}

3靜態(tài)內(nèi)部類中創(chuàng)建了一個靜態(tài)實例,會導致內(nèi)存泄漏(參考上面單例導致的內(nèi)存泄漏)

3.資源回收

對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷,否則這些資源將不會被回收,從而造成內(nèi)存泄漏。

4ListView

初始時ListView會從BaseAdapter中根據(jù)當前的屏幕布局實例化一定數(shù)量的View對象,同時ListView會將這些View對象緩存起來。當向上滾動ListView時,原先位于最上面的Item的View對象會被回收,然后被用來構(gòu)造新出現(xiàn)在下面的Item。這個構(gòu)造過程就是由getView()方法完成的,getView()的第二個形參convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。構(gòu)造Adapter時,沒有使用緩存的convertView。

5webview

我們不要使用WebView對象時,應(yīng)該調(diào)用它的destory()函數(shù)來銷毀它,并釋放其占用的內(nèi)存,否則其長期占用的內(nèi)存也不能被回收,從而造成內(nèi)存泄露。

6集合

我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

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

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

  • 分三步說明Android內(nèi)存泄漏的原因及解決,“內(nèi)存泄漏與內(nèi)存溢出的區(qū)別”,“引用方式”,“常見引發(fā)原因與解決方案...
    我的天吶0_0閱讀 822評論 0 2
  • 目錄介紹 0.關(guān)于四種引用0.1 引用說明0.2 關(guān)于Java下ref包和Android下ref包 1.強引用1....
    楊充211閱讀 1,431評論 1 2
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,700評論 0 8
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講,...
    DreamFish閱讀 868評論 0 5
  • 明天開全縣的教育教學工作會議,沒通知我任何角色,但我知道會議中有我工作室的事。萬一有特殊的因素下,領(lǐng)導不清楚工作室...
    回歸本心閱讀 124評論 0 0

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