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得不到及時的釋放。等等,還須要進一步地研究