1、 Android的 java程序為什么容易出現OOM
這個問題要涉及到android系統的設置方面了,主要因為Android系統對 dalvik
的 vm heapsize 作了硬性限制,當java進程申請的java空間超過閾值時,就會拋出OOM異常(這個閾值可以是48M、24M、16M等,視機型而定),可以通過命令adb shell getprop | grep dalvik.vm.heapgrowthlimit
查看此值。也可在代碼中通過api來獲取該數值
ActivityManager activityManager =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryClass();
以上方法會返回以M為單位的數字,不同的系統平臺或設備上的值都不太一樣。
而查看一個進程的內存使用情況,可以通過dumpsys meminfo
命令可以查看,如adb shell dumpsys meminfo com.test.wonder
,后面為應用的包名。
也就是說,程序發生OMM并不表示RAM不足,而是因為程序申請的 java heap 對象超過了 dalvik vm heapgrowthlimit 。所以即使在RAM充足的情況下,也可能發生OOM問題。
2、導致OOM問題的原因有哪些?
Android內存泄漏的八種可能(上)
Android防止內存泄漏的八種方法(下)
ps:這里介紹幾個比較常見的:
1 、應用中需要加載大對象,例如Bitmap
在android 2.3和以前的版本,bitmap對象的像素數據都是分配在native heap中的,所以我們在調試過程中這部分內存是在java heap中看不到的,不過在android 3.0之后,bitmap對象就直接分配在java heap上了,這樣便于調試和管理。因此在3.0之后我們可以復用bitmap的內存,而不必回收它,不過新的bitmap對象要大小和原來的一樣,到了android 4.4之后,就只要高寬不超過原來的就行了。
Android中一張圖片(BitMap)占用的內存的計算: Android中一張圖片(BitMap)占用的內存主要和以下幾個因數有關:圖片長度,圖片寬度,單位像素占用的字節數。
一張圖片(BitMap)占用的內存 = 圖片長度 × 圖片寬度 × 單位像素占用的字節數,其單位像素占用的字節數由其參數BitmapFactory.Options的inPreferredConfig變量決,inPreferredConfig為Bitmap.Config類型,Bitmap.Config類是個枚舉類型,有四種取值方式,Android手機上一個BitMap的默認格式為ARGB_8888
格式,也是主要使用格式,它的單位像素占用四個字節。
比如,一張在pc機上用的1024*768圖片,如果直接用在手機屏幕這種小屏幕上,不僅沒有提高顯示質量,還容易使內存吃緊。假設照片是用ARGB_8888格式,那么一張1024×768的圖片需要占用3M的內存, 4-5張就OOM了。bitmap分辨率越高,所占用的內存就越大,這個是以2為指數級增長的。
2 、不合理使用Context 導致的Memory leak
android 中很多地方都要用到context,連基本的Activty 和 Service都是從Context派生出來的,我們利用Context主要用來加載資源或者初始化組件,在Activity中有些地方需要用到Context的時候,我們經常會把context給傳遞過去了,將context傳遞出去就有可能延長了context的生命周期,最終導致了內存泄漏。例如 我們將activty context對象傳遞給一個后臺線程去執行某些操作,如果在這個過程中因為屏幕旋轉而導致activity重建,那么原先的activity對象不會被回收,因為它還被后臺線程引用著,如果這個activity消耗了比較多的內存,那么新建activity或者后續操作可能因為舊的activity沒有被回收而導致內存泄漏。所以,遇到需要用到context的時候,我們要合理選擇不同的context,對于android應用來說還有一個單例的Application Context對象,該對象生命周期和應用的生命周期是綁定的。選擇context應該考慮到它的生命周期,如果使用該context的組件的生命周期超過該context對象,那么我們就要考慮是否可以用application context。如果真的需要用到該context對象,可以考慮用弱引用來WeakReference來避免內存泄漏。
3、 非靜態內部類導致的Memory leak
非靜態的內部類會持有外部類的一個引用,所以和前面context說到的一樣,如果該內部類生命周期超過外部類的生命周期,就可能引起內存泄露了,如AsyncTask ,Thread ,TimerTask和Handler,這里有篇關于Handler使用不當而導致oom的文章,講的很清楚。
前一段時間專門對項目中使用到的Handler進行了盤查改善來修改和預防oom問題,我們是在代碼中是通過軟引用來實現的:
public interface SafeHandlerListener {
void handMessage(Message msg);
}
public class SafeHandler extends Handler {
private final WeakReference<SafeHandlerListener> ref;
public SafeHandler(SafeHandlerListener listener) {
ref = new WeakReference<>(listener);
}
@Override
public void handleMessage(Message msg) {
SafeHandlerListener caller = ref.get();
if (caller != null){
caller.handMessage(msg);
}
}
}
由于在Activity中我們經常會用到內部類,所以要小心管理其生命周期。 如果明確生命周期較外部類長的話,那么應該使用靜態內部類或者考慮用弱引用WeakReference來避免內存泄漏。