java.lang.OutOfMemoryError異常解決方法

引言

java.lang.OutOfMemoryError簡稱OOM內存溢出,這是一種很常見的致使的程序崩潰的問題,但也是很容易被開發者忽視的一個問題,由於它不像java.lang.NullPointerException這樣的錯誤,程序一運行就能被發現,它不是每次運行或每臺手機都出現,有時可能要等到項目上線,後臺產生了大量數據以後才能被發現。
最近作了一個新聞類的項目,和商城類的項目相比,因爲涉及更多的圖片和視頻,很明顯數據量要大得多,因此更容易產生OOM的問題。如何解決這個異常呢,首先咱們來看看它是怎麼產生的。java

產生緣由

常見緣由有如下幾種:
1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
3.代碼中存在死循環或循環產生過多重複的對象實體;
4.使用的第三方軟件中的BUG;
5.啓動參數內存值設定的太小;web

對應的Log顯示的錯誤提示以下:
1.tomcat:java.lang.OutOfMemoryError: PermGen space
2.tomcat:java.lang.OutOfMemoryError: Java heap space
3.weblogic:Root cause of ServletException java.lang.OutOfMemoryError
4.resin:java.lang.OutOfMemoryError
5.java:java.lang.OutOfMemoryError數據庫

解決方案

1.避免循環產生過多重複的對象實體tomcat

我的以爲對於這個問題開發程序時應該是能夠避免的,即便寫程序時沒有考慮到,發現是這個緣由形成的,應該也是很容易解決的;
例如,對複雜的listview進行合理設計與編碼,注意重用Adapter裏面的convertView,以及holder機制的運用網絡

2.自定義內存大小和優化Dalvik虛擬機的堆內存ide

在Android2.2以前,有這樣一個類dalvik.system.VMRuntime,它提供的setTargetHeapUtilization()方法能夠加強程序堆內存的處理效率,代碼以下:佈局

//在程序onCreate時就能夠調用便可優化

private final static floatTARGET_HEAP_UTILIZATION = 0.75f;  
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);

還有setMinimumHeapSize()方法能夠自定義應用須要多大的內存,強行設置最小內存大小,
//設置最小heap內存爲6MB大小this

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; 
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

可是上面說了這種方法只適用於Android2.2如下,不然編譯是通不過的,既然取消了這個方法,確定有可以代替這個方法,且比這個方法更完美的解決方案,這就是下面我要重點說的幾種方法;編碼

3.Fragment的懶加載

如今大多數程序一個Activity裏面可能會以viewpager(或其餘容器)與多個Fragment來組合使用,而若是每一個fragment都須要去加載數據,或從本地加載,或從網絡加載,那麼在這個activity剛建立的時候就變成須要初始化大量資源。那麼,能不能作到當切換到這個fragment的時候,它纔去初始化呢?

答案就在Fragment裏的setUserVisibleHint這個方法裏,該方法用於告訴系統,這個Fragment的UI是不是可見的。因此咱們只須要繼承Fragment並重寫該方法,便可實如今fragment可見時才進行數據加載操做,即Fragment的懶加載。

代碼以下:

public abstract class LazyFragment extends Fragment {  
        protected boolean isVisible;
    @Override  
    public void setUserVisibleHint(boolean isVisibleToUser) {  
        super.setUserVisibleHint(isVisibleToUser);  
        if(getUserVisibleHint()) {  
            isVisible = true;  
            onVisible();  
        } else {  
            isVisible = false;  
            onInvisible();  
        }  
    }  
  
    protected void onVisible(){  
        lazyLoad();  
    }  
  
    protected abstract void lazyLoad();  
  
    protected void onInvisible(){}  
}

在LazyFragment,我增長了三個方法,一個是onVisiable,即fragment被設置爲可見時調用,一個是onInvisible,即fragment被設置爲不可見時調用。另外再寫了一個lazyLoad的抽象方法,該方法在onVisible裏面調用。

你可能會想,爲何不在getUserVisibleHint裏面就直接調用呢?我這麼寫是爲了代碼的複用。由於在fragment中,咱們還須要建立視圖(onCreateView()方法),可能還須要在它不可見時就進行其餘小量的初始化操做(好比初始化須要經過AIDL調用的遠程服務)等。

而setUserVisibleHint是在onCreateView以前調用的,那麼在視圖未初始化的時候,在lazyLoad當中就使用的話,就會有空指針的異常。而把lazyLoad抽離成一個方法,那麼它的子類就能夠這樣作:

public class OpenResultFragment extends LazyFragment{  
    // 標誌位,標誌已經初始化完成。  
    private boolean isPrepared;  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        Log.d(LOG_TAG, "onCreateView");  
        View view = inflater.inflate(R.layout.fragment_open_result, container, false);  
        //XXX初始化view的各控件  
    isPrepared = true;  
        lazyLoad();  
        return view;  
    }  
  
    @Override  
    protected void lazyLoad() {  
        if(!isPrepared || !isVisible) {  
            return;  
        }  
        //填充各控件的數據  
    }  
  
}

在上面的類當中,咱們增長了一個標誌位isPrepared,用於標誌是否初始化完成。而後在咱們所須要的初始化操做完成以後調用,如上面的例子當中,在初始化view以後,設置 isPrepared爲true,同時調用lazyLoad()方法。而在lazyLoad()當中,判斷isPrepared和isVisible只要有一個不爲true就不往下執行。也就是僅當初始化完成,而且可見的時候才繼續加載,這樣的避免了未初始化完成就使用而帶來的問題。

4.利用BitmapFactory.Options限制圖片的大小

有時加載數據量並非很大時也會產生OOM異常,通常是因爲加載圖片形成的,Bitmap加載圖片最終都是經過java層的createBitmap來完成的,須要消耗大量內存,咱們能夠利用BitmapFactory.Options限制圖片的大小,下降圖片質量,減小圖片所佔內存

InputStream is = this.getResources().openRawResource(R.drawable.pic1); 
BitmapFactory.Options options=new BitmapFactory.Options(); 
options.inJustDecodeBounds = false; 
options.inSampleSize = 10;   //width,hight設爲原來的十分一 
Bitmap btp =BitmapFactory.decodeStream(is,null,options);

5.及時對圖片進行recyle()操做

if(!bmp.isRecycle() ){ 
         bmp.recycle()   //回收圖片所佔的內存 
         system.gc()  //提醒系統及時回收 
}

總結

以上是OOM的常見的幾種產生緣由和解決方案,還有不少方法,例如,還有看看頁面佈局當中有沒有大的圖片,好比背景圖之類的。去除xml中相關設置,改在程序中設置背景圖(放在onCreate()方法中);儘可能不使用靜態的圖片和全局性的圖片;在Activity destory時注意,bg.setCallback(null); 防止Activity得不到及時的釋放。等等,還須要進一步地研究