網站首頁 編程語言 正文
背景知識
Android中每個App默認情況下是運行在一個獨立進程中的, 而這個獨立進程正是從Zygote孵化出來的VM進程, 也就是說, 也就是說每個Android APP在運行時會啟動一個Java虛擬機。
并且系統會給它分配固定的內存空間(手機廠商會根據手機的配置情況來對其進行調整)。
一、Android VM的內存空間
Android是一個多任務系統, 為了保證多任務的運行, Android給每個App可使用的Heap大小設定了一個限定值.
這個值是系統設置的prop值, 保存在System/build.prop
文件中. 一般國內的手機廠商都會做修改, 根據手機配置不同而不同, 可以直接打開查看與修改。
其中和虛擬機內存相關的主要有以下三個:
1 .?dalvik.vm.heapstartsize
– App啟動后,系統分配給它的Heap初始大小,隨著App使用可增加。
2 .?dalvik.vm.heapgrowthlimit
– 默認情況下, App可使用的Heap的最大值, 超過這個值就會產生OOM.
3 .?dalvik.vm.heapsize
– 如果App的manifest文件中配置了largeHeap屬性, 那么App可使用的Heap的最大值為此項設定值。
<application android:largeHeap="true"> ... </application>
所以對于同一個手機,不開啟largeHeap
屬性時與多進程時,每個APP的虛擬機分配的內存的上限都是heapgrowthlimit
。
1.查看內存的API
Android在ActivityManager類中提供了API可以運行時獲取這些屬性值,如下:
//ActivityManager的getMemoryClass()獲得內用正常情況下內存的大小,即heapgrowthlimit的值 //ActivityManager的getLargeMemoryClass()可以獲得開啟largeHeap最大的內存大小,即heapsize的指 ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryClass(); activityManager.getLargeMemoryClass();
二、Android VM內存分配流程
虛擬機分配內存的具體源碼可以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; }
具體流程如下:
- 嘗試分配,如果成功則返回,失敗則轉入步驟2
- 判斷是否gc正在進行垃圾回收,如果正在進行則等待回收完成之后,嘗試分配。如果成功則返回,失敗則轉入步驟3
- 自己啟動gc進行垃圾回收,這里gcForMalloc的參數是false。所以不會回收軟引用,回收完成后嘗試分配,如果成功則返回,失敗則轉入步驟4
- 調用dvmHeapSourceAllocAndGrow嘗試分配,這個函數會擴張堆的大小,失敗轉入步驟5
- 進入回收軟引用階段,這里gcForMalloc的參數是ture,所以需要回收軟引用。然后再調用dvmHeapSourceAllocAndGrow嘗試分配,如果失敗則拋出OOM
小結
所以產生OOM時,一定是java的堆中?已有的內存 + 申請的內存 >= heapgrowthlimit導致的,不會因為手機目前物理內存是否緊張而改變 - 當物理內存非常緊張時系統會通過LowMemory Killer殺掉一些低優先級的進程。
相應的,物理內存非常充足的情況也會有OOM的情況發生。
三、出現OOM的建議解決方案
當APP出現OOM時,建議可以從以下兩個方向來處理:
1 .?排查內存泄露問題
排查各個功能是否內存泄露情況,可以通過Android Studio中的MemoryMonitor功能進行分析,Memory Monitor也集成了HPROF Viewer和Allocation Tracker可以分析內存快照與內存分配追蹤。另外推薦一個工具,square公司開源的leakcanary,非常簡潔好用。
- 排查進程初始化時就直接申請并常駐內存的對象以及其他功能里申請的static對象或者單例對象的必要性。
2 .?內存優化
按照谷歌在youtube上發布的性能優化典范之內存篇,優化各功能的內存,或可參照 胡凱的總結 。
大致有以下這些,具體請參見原文:
- 謹慎使用large heap
- 綜合考慮設備的內存閾值與其他因素設計合適的緩存大小
- onLowMemory與onTrimMemory
- 資源文件需要選擇合適的文件夾進行存放
- Try catch某些大內存分配的操作
- 謹慎使用static對象
- 特別留意單例對象中不合理的持有
- 珍惜Services資源
- 優化布局層次,減少內存消耗
- 謹慎使用“抽象”編程
- 使用nano protobufs序列化數據
- 謹慎使用依賴注入框架
- 謹慎使用多進程
- 使用ProGuard來剔除不需要的代碼
- 謹慎使用第三方libraries
考慮不同的實現方式來優化內存占用
- 注意Activity的泄露
- 考慮使用Applicaiton Context代替Activity Context
- 注意臨時Bitmap對象的及時回收
- 注意監聽器的注銷
- 注意緩存容器里的對象泄露
- 注意Webview的泄露
注意Cursor對象的及時關閉
- 復用系統自帶的資源
- ListView中對ConvertView的復用
- Bitmap對象的復用
- 避免在ondraw方法里執行對象的創建
StringBuilder代替String
- 使用更加輕量的數據結構
- 避免在Android里使用enum
- 減少Bitmap對象的內存占用
使用更小的圖片
- 減少對象的內存占用
- 內存對象的重復利用
- 避免對象的內存泄露
- 內存使用策略的優化
原文鏈接:https://www.androidos.net.cn/doc/2022/8/27/958.html
相關推薦
- 2022-11-29 Redis五大常用數據結構-string、list、set、hash、zset(筆記)
- 2022-05-20 Idea搭建一個簡單的SpringBoot項目
- 2023-02-18 Nginx中Location配置超詳細講解_nginx
- 2022-07-23 聊聊配置?Nginx?訪問與錯誤日志的問題_nginx
- 2022-11-24 使用sqlserver官方驅動包調用存儲過程遇到的坑及解決方法_MsSql
- 2022-03-26 C語言中指針常量和常量指針的區別_C 語言
- 2022-08-30 MQTT - 消息隊列遙測傳輸協議
- 2022-06-01 C#類型轉換之自定義隱式轉換和顯式轉換_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支