Android 虛擬機(jī)中的內(nèi)存分配與OOM問題詳解
背景知識
Android中每個App默認(rèn)情況下是運(yùn)行在一個獨(dú)立進(jìn)程中的, 而這個獨(dú)立進(jìn)程正是從Zygote孵化出來的VM進(jìn)程, 也就是說, 也就是說每個Android APP在運(yùn)行時會啟動一個Java虛擬機(jī)。
并且系統(tǒng)會給它分配固定的內(nèi)存空間(手機(jī)廠商會根據(jù)手機(jī)的配置情況來對其進(jìn)行調(diào)整)。
一、Android VM的內(nèi)存空間
Android是一個多任務(wù)系統(tǒng), 為了保證多任務(wù)的運(yùn)行, Android給每個App可使用的Heap大小設(shè)定了一個限定值.
這個值是系統(tǒng)設(shè)置的prop值, 保存在System/build.prop文件中. 一般國內(nèi)的手機(jī)廠商都會做修改, 根據(jù)手機(jī)配置不同而不同, 可以直接打開查看與修改。
其中和虛擬機(jī)內(nèi)存相關(guān)的主要有以下三個:
1 . dalvik.vm.heapstartsize
– App啟動后,系統(tǒng)分配給它的Heap初始大小,隨著App使用可增加。
2 . dalvik.vm.heapgrowthlimit
– 默認(rèn)情況下, App可使用的Heap的最大值, 超過這個值就會產(chǎn)生OOM.
3 . dalvik.vm.heapsize
– 如果App的manifest文件中配置了largeHeap屬性, 那么App可使用的Heap的最大值為此項設(shè)定值。
<application
android:largeHeap="true">
...
</application>所以對于同一個手機(jī),不開啟largeHeap屬性時與多進(jìn)程時,每個APP的虛擬機(jī)分配的內(nèi)存的上限都是heapgrowthlimit。
1.查看內(nèi)存的API
Android在ActivityManager類中提供了API可以運(yùn)行時獲取這些屬性值,如下:
//ActivityManager的getMemoryClass()獲得內(nèi)用正常情況下內(nèi)存的大小,即heapgrowthlimit的值 //ActivityManager的getLargeMemoryClass()可以獲得開啟largeHeap最大的內(nèi)存大小,即heapsize的指 ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryClass(); activityManager.getLargeMemoryClass();
二、Android VM內(nèi)存分配流程
虛擬機(jī)分配內(nèi)存的具體源碼可以AOSP的Heap.cpp文件中查看:
/* Try as hard as possible to allocate some memory.
*/
static void *tryMalloc(size_t size)
{
void *ptr;
//TODO: figure out better heuristics
// There will be a lot of churn if someone allocates a bunch of
// big objects in a row, and we hit the frag case each time.
// A full GC for each.
// Maybe we grow the heap in bigger leaps
// Maybe we skip the GC if the size is large and we did one recently
// (number of allocations ago) (watch for thread effects)
// DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other
// (or, at least, there are only 0-5 objects swept each time)
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/*
* The allocation failed. If the GC is running, block until it
* completes and retry.
*/
if (gDvm.gcHeap->gcRunning) {
/*
* The GC is concurrently tracing the heap. Release the heap
* lock, wait for the GC to complete, and retrying allocating.
*/
dvmWaitForConcurrentGcToComplete();
} else {
/*
* Try a foreground GC since a concurrent GC is not currently running.
*/
gcForMalloc(false);
}
ptr = dvmHeapSourceAlloc(size);
if (ptr != NULL) {
return ptr;
}
/* Even that didn't work; this is an exceptional state.
* Try harder, growing the heap if necessary.
*/
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
size_t newHeapSize;
newHeapSize = dvmHeapSourceGetIdealFootprint();
//TODO: may want to grow a little bit more so that the amount of free
// space is equal to the old free space + the utilization slop for
// the new allocation.
LOGI_HEAP("Grow heap (frag case) to "
"%zu.%03zuMB for %zu-byte allocation",
FRACTIONAL_MB(newHeapSize), size);
return ptr;
}
/* Most allocations should have succeeded by now, so the heap
* is really full, really fragmented, or the requested size is
* really big. Do another GC, collecting SoftReferences this
* time. The VM spec requires that all SoftReferences have
* been collected and cleared before throwing an OOME.
*/
//TODO: wait for the finalizers from the previous GC to finish
LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation",
size);
gcForMalloc(true);
ptr = dvmHeapSourceAllocAndGrow(size);
if (ptr != NULL) {
return ptr;
}
//TODO: maybe wait for finalizers and try one last time
LOGE_HEAP("Out of memory on a %zd-byte allocation.", size);
//TODO: tell the HeapSource to dump its state
dvmDumpThread(dvmThreadSelf(), false);
return NULL;
}具體流程如下:
- 嘗試分配,如果成功則返回,失敗則轉(zhuǎn)入步驟2
- 判斷是否gc正在進(jìn)行垃圾回收,如果正在進(jìn)行則等待回收完成之后,嘗試分配。如果成功則返回,失敗則轉(zhuǎn)入步驟3
- 自己啟動gc進(jìn)行垃圾回收,這里gcForMalloc的參數(shù)是false。所以不會回收軟引用,回收完成后嘗試分配,如果成功則返回,失敗則轉(zhuǎn)入步驟4
- 調(diào)用dvmHeapSourceAllocAndGrow嘗試分配,這個函數(shù)會擴(kuò)張堆的大小,失敗轉(zhuǎn)入步驟5
- 進(jìn)入回收軟引用階段,這里gcForMalloc的參數(shù)是ture,所以需要回收軟引用。然后再調(diào)用dvmHeapSourceAllocAndGrow嘗試分配,如果失敗則拋出OOM
小結(jié)
所以產(chǎn)生OOM時,一定是java的堆中 已有的內(nèi)存 + 申請的內(nèi)存 >= heapgrowthlimit導(dǎo)致的,不會因為手機(jī)目前物理內(nèi)存是否緊張而改變 - 當(dāng)物理內(nèi)存非常緊張時系統(tǒng)會通過LowMemory Killer殺掉一些低優(yōu)先級的進(jìn)程。
相應(yīng)的,物理內(nèi)存非常充足的情況也會有OOM的情況發(fā)生。
三、出現(xiàn)OOM的建議解決方案
當(dāng)APP出現(xiàn)OOM時,建議可以從以下兩個方向來處理:
1 . 排查內(nèi)存泄露問題
排查各個功能是否內(nèi)存泄露情況,可以通過Android Studio中的MemoryMonitor功能進(jìn)行分析,Memory Monitor也集成了HPROF Viewer和Allocation Tracker可以分析內(nèi)存快照與內(nèi)存分配追蹤。另外推薦一個工具,square公司開源的leakcanary,非常簡潔好用。
- 排查進(jìn)程初始化時就直接申請并常駐內(nèi)存的對象以及其他功能里申請的static對象或者單例對象的必要性。
2 . 內(nèi)存優(yōu)化
按照谷歌在youtube上發(fā)布的性能優(yōu)化典范之內(nèi)存篇,優(yōu)化各功能的內(nèi)存,或可參照 胡凱的總結(jié) 。
大致有以下這些,具體請參見原文:
- 謹(jǐn)慎使用large heap
- 綜合考慮設(shè)備的內(nèi)存閾值與其他因素設(shè)計合適的緩存大小
- onLowMemory與onTrimMemory
- 資源文件需要選擇合適的文件夾進(jìn)行存放
- Try catch某些大內(nèi)存分配的操作
- 謹(jǐn)慎使用static對象
- 特別留意單例對象中不合理的持有
- 珍惜Services資源
- 優(yōu)化布局層次,減少內(nèi)存消耗
- 謹(jǐn)慎使用“抽象”編程
- 使用nano protobufs序列化數(shù)據(jù)
- 謹(jǐn)慎使用依賴注入框架
- 謹(jǐn)慎使用多進(jìn)程
- 使用ProGuard來剔除不需要的代碼
- 謹(jǐn)慎使用第三方libraries
考慮不同的實現(xiàn)方式來優(yōu)化內(nèi)存占用
- 注意Activity的泄露
- 考慮使用Applicaiton Context代替Activity Context
- 注意臨時Bitmap對象的及時回收
- 注意監(jiān)聽器的注銷
- 注意緩存容器里的對象泄露
- 注意Webview的泄露
注意Cursor對象的及時關(guān)閉
- 復(fù)用系統(tǒng)自帶的資源
- ListView中對ConvertView的復(fù)用
- Bitmap對象的復(fù)用
- 避免在ondraw方法里執(zhí)行對象的創(chuàng)建
StringBuilder代替String
- 使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
- 避免在Android里使用enum
- 減少Bitmap對象的內(nèi)存占用
使用更小的圖片
- 減少對象的內(nèi)存占用
- 內(nèi)存對象的重復(fù)利用
- 避免對象的內(nèi)存泄露
- 內(nèi)存使用策略的優(yōu)化
以上就是Android 虛擬機(jī)中的內(nèi)存分配與OOM問題詳解的詳細(xì)內(nèi)容,更多關(guān)于Android虛擬機(jī)內(nèi)存分配OOM的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Flutter進(jìn)階之實現(xiàn)動畫效果(四)
這篇文章主要為大家詳細(xì)介紹了Flutter進(jìn)階之實現(xiàn)動畫效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-08-08
Android 封裝Okhttp+Retrofit+RxJava,外加攔截器實例
下面小編就為大家分享一篇Android封裝Okhttp+Retrofit+RxJava,外加攔截器實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android加載大分辨率圖片到手機(jī)內(nèi)存中的實例方法
有些圖片的分辨率比較高,把它直接加載到手機(jī)內(nèi)存中之后,會導(dǎo)致堆內(nèi)存溢出的問題,下面就講解一下Android的堆內(nèi)存以及如何在Android應(yīng)用中加載一個高分辨率的圖片的方法2013-11-11
Android組件創(chuàng)建DrawerLayout導(dǎo)航
這篇文章主要為大家詳細(xì)介紹了Android組件創(chuàng)建DrawerLayout導(dǎo)航的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
詳解AndroidStudio JNI +Gradle3.0以上JNI爬坑之旅
這篇文章主要介紹了詳解AndroidStudio JNI +Gradle3.0以上JNI爬坑之旅,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Android RecyclerView設(shè)置下拉刷新的實現(xiàn)方法
這篇文章主要介紹了Android RecyclerView設(shè)置下拉刷新的實現(xiàn)方法,希望通過本文通過SwipeRefreshLayout方式實現(xiàn)下拉刷新,需要的朋友可以參考下2017-10-10
關(guān)于Kotlin委托你必須重視的幾個點(diǎn)
委托模式已經(jīng)被證明是實現(xiàn)繼承的一個很好的替代方式,下面這篇文章主要給大家介紹了關(guān)于Kotlin委托你必須重視的幾個點(diǎn),文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01

