分類專欄:抄底 Android 內(nèi)存優(yōu)化文章標(biāo)簽:Android內(nèi)存優(yōu)化線上監(jiān)控
版權(quán)聲明:本文為博主原創(chuàng)文章层宫,遵循<a target="_blank"rel="noopener"> CC 4.0 BY-SA </a>版權(quán)協(xié)議挂捅,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/qq_23191031/article/details/109457009
此文在快手Android OOM治理實踐與思考的基礎(chǔ)上進行了自己的剖析和引導(dǎo)
如果您閱讀到此文章,請您帶著批判的眼光看待內(nèi)容,如有錯誤或不嚴(yán)謹(jǐn)?shù)牡胤秸埪?lián)系我,我將不盡感激:im_dsd@126.com搬葬,謝謝。
Java
Native
Thread
File Descriptor
JNI Reference
以 Bitmap 為例艳悔,創(chuàng)建 720P 的 Bitmap 需要 3M 內(nèi)存急凰,剩余 Java Heap 只有 1M,引發(fā)
OOM猜年。在開啟 largeHeap 的情況下抡锈,絕大多數(shù)設(shè)備的 Java Heap 上限是 512M(由dalvik.vm.heapsize
屬性決定),64位設(shè)備還會更大一些乔外,合理使用完全滿足床三。
Android 8.0 開始 Bitmap 分配在 Native Heap, OOM 率大幅度降低杨幼。
虛擬機為 Java 線程分配的椘膊荆空間為 1040KB(1024 + 兩個8kb的保護段),絕大多數(shù)的情況下都是浪費的
由于 Java 代碼無法在 Stack 上分配對象差购,所以 Java的 Stack OOM 多數(shù)因為死循環(huán)遞歸調(diào)用引起的
JNI 的情況復(fù)雜一些四瘫, C 代碼可以在棧上分配大內(nèi)存,即使內(nèi)存使用得當(dāng)也可能造成匪夷所思的棧溢出欲逃,比如誤用了其他線程的 JVIEnv找蜜。
Native VSS OOM(VSS:Virtual Set Size 虛擬內(nèi)存)
VSS(包含共享庫占用的內(nèi)存) OOM 指的是虛擬內(nèi)存 OOM,按理說虛擬地址是無限的暖夭,為啥會出現(xiàn) OOM 呢锹杈?原因在于尋址空間:
64 位設(shè)備的 kernal 運行于 64位模式尋址空間我們可以認(rèn)為是無限的撵孤,而32位 app 的尋址空間實際只有 4G
虛擬機會占用
1 ~ 2G 的 VSS 用于 GC 等操作迈着,因此對于32位應(yīng)用竭望, Java Heap 越大,反而越容易觸發(fā) VSS
OOM裕菠。這個錯誤容易出現(xiàn)在重度依賴 Native 的 app 上咬清,變現(xiàn)為:越是高端機(分辨率高、Java Heap高)OOM 率越高的怪異現(xiàn)象奴潘。
這個錯誤在 Native 層會返回 12 或者 -12 錯誤碼旧烧。
Native PSS OOM (PSS: Proportional Set Size 物理內(nèi)存)
PSS(比例分配共享庫占用的內(nèi)存) 不像是 Java Heap 有單進程的上線,而是根據(jù)設(shè)備總體內(nèi)存情況決定画髓。
LowMemoryKiller
守護進程(LMKD)會每隔一段事件檢查設(shè)備的內(nèi)存使用情況掘剪,當(dāng)內(nèi)存不足的時候,會依據(jù)進程優(yōu)先級從低到高依次殺進程釋放內(nèi)存奈虾,保證系統(tǒng)服務(wù)和前臺應(yīng)用有足夠的內(nèi)存夺谁。
比如我們在一個4G物理內(nèi)存的手機上,使用 Native 分配 2G 內(nèi)存肉微,很大概率 App 會被 LMK 殺死匾鸥,而且沒有任何提示。
在 Java 調(diào)用 Thread#start 方法之后碉纳,Native層就會創(chuàng)建一個 Thread勿负,創(chuàng)建 Thread 需要在 Java Stack 上分配一個1040kb的空間作為線程棧。
上圖可以看到 Thread OOM 會提示 ptread_create 失敗劳曹,Thread OOM 主要出現(xiàn)兩種情況:
文件描述符不夠了
虛擬內(nèi)存的尋址空間不夠了奴愉。
下圖是 ART 虛擬機線程棧的內(nèi)存布局圖,其中綠色部分就是一個線程需要的 Stack 內(nèi)存空間: 1024kb铁孵,再加上 ART 提供的內(nèi)存保護:兩個8KB躁劣,總共是1040KB
線程數(shù)的上線系統(tǒng)默認(rèn)是 32768 部分華為設(shè)備的限制是500, 也是 VSS OOM的一種表現(xiàn)形式库菲。通常1000(1000 * 1040k ≈ 1G)左右就會觸發(fā) VSS OOM账忘。因此Thread OOM其實也是 VSS OOM的一種表現(xiàn)形式。
通過 hook libart.so 的 pthread_attr_setstacksize 外部調(diào)用熙宇, 可以減小創(chuàng)建線程時分配的內(nèi)存比如 256KB, 根據(jù)快手技術(shù)專家的經(jīng)驗 256kb 已經(jīng)足夠用了鳖擒,大幅減低Thread OOM率。
同理烫止,反射減小sCursorWindowSize蒋荚,可以大幅降低數(shù)據(jù)庫訪問時的OOM。
在 Android 9 以前 FD 是寶貴的資源馆蠕,系統(tǒng)默認(rèn)設(shè)定一個進程最多分配 1024 個FD期升,可以通過 ulimit -a 查看惊奇。到了 Android 9 Google 工程師也意識到這個設(shè)定有點小了已經(jīng)拓展到 32768 (3k個)
Anrdoid 9 以前Android 9
我們很多應(yīng)用場景都會默默的使用
FD 例如 Handler、stock 等比如創(chuàng)建一個 HandlerThread 在創(chuàng)建線程的同時還會創(chuàng)建兩個
FD:mWeakEventFd和mEpollEvent(原因是 Handler 內(nèi)使用了 epoll 機制來保障 Looper 的等待和喚醒播赁,在
Android 5.0之前 Handler 的底層使用的是管道此時會使用三個 FD)颂郎。
/proc//fd 根據(jù)文件名聚類可以初步定位fd泄漏問題。(此操作需要 Root)
JNIT 的引用 OOM 可以在 logcat 中查看容为。在 Android 日常開發(fā)中很少會遇到 JNI Reference OOM 場景乓序,除非你是 Native 的開發(fā)人員。
解決內(nèi)存問題最有效的辦法就是通過內(nèi)存鏡像(Hprof文件)
LeakCanary 的核心也是解決內(nèi)存問題常見的三板斧:監(jiān)控泄漏坎背、采集鏡像替劈、分析鏡像。
但是 LeakCanary 有致命的問題:無法線上部署得滤。原因很簡單在采集和分析內(nèi)存鏡像中會造成明顯的卡頓陨献。而且官方文檔也明確建議不推薦在線上版本使用,如果集成到 release 包內(nèi)會直接觸發(fā)崩潰懂更。
Wedo not recommendincluding LeakCanary in release builds, as it could negatively impact the experience of your customers.
無法線上部署
主動觸發(fā) GC 造成 App 卡頓
Dump 內(nèi)存鏡像時會造成 app 凍結(jié)(大約13s眨业,期間不響應(yīng)任何操作)
分析內(nèi)存鏡像效率低
不具備 service 處理能力(上傳、檢索膜蛔、分析)
使用范圍
僅能檢查 Activity&Fragment泄漏
無法檢測大對象分配坛猪、內(nèi)心抖動等問題
自動化程度低
無法對問題做聚類分發(fā)
需要人工買點
LeakCanary dump 操作使用了 Android ApiDebug.dumpHprofData(String fileName):
為什么Debug.dumpHprofData(String fileName)會凍結(jié) App
dump 的操作能不能異步完成?
dump 操作會通過 Native 層的 hprof.Dump() 將內(nèi)存數(shù)據(jù)按照 Hprof 協(xié)議的格式按照二進制的形式保存到磁盤中
為了保證
dump 過程中內(nèi)存數(shù)據(jù)的不變性在執(zhí)行 hprof.Dump() 之前會通過 ScopedSuspendAll (構(gòu)造函數(shù)內(nèi)調(diào)用了
SupendAll)暫停了所有 Java 線程皂股,在 dump 結(jié)束后通過 ScopedSusendAll 析構(gòu)函數(shù)中(通過 ResumeAll
)恢復(fù)線程
既然要凍結(jié)所有線程那么常規(guī)的子線程操作就沒了任何意義墅茉。那么在子進程中處理可以嗎?這里要設(shè)計一個知識點 :進程的創(chuàng)建過程呜呐。
Android 是在 Linux 內(nèi)核基礎(chǔ)上構(gòu)建的操作系統(tǒng)就斤,所以 Android 創(chuàng)建進程的流程大體如下:
子進程都由父進程 fork 而來(Android 為 App 創(chuàng)建進程時 fork 的是 zygote 進程),且符合 COW 流程(copy-on-write 寫時復(fù)制)
COW指的子進程在創(chuàng)建時拷貝的是父進程的虛擬內(nèi)存而不是物理內(nèi)存蘑辑。子進程擁有了父進程的虛擬內(nèi)存就可以通過頁表共享父進程的物理內(nèi)存(這是關(guān)鍵)
當(dāng)子進程或者父進程對同享的物理內(nèi)存修改時洋机,內(nèi)核會攔截此過程,將共享的內(nèi)存以頁為單位進行拷貝洋魂,父進程將保留原始物理空間绷旗,而子進程將會使用拷貝后的新物理內(nèi)存。
這意味著我們可以在 dump 之前先 fork App 進程副砍,這樣子進程就獲得了父進程所有的內(nèi)存鏡像衔肢。但還有一個棘手的問題需要解決。
這個棘手的問題是:dump 前需要暫停所有線程(不僅僅是 Java 線程還有 Native 線程)豁翎,而子進程只保留執(zhí)行 fork
操作的進程角骤,但主進程內(nèi)所有的線程信息還是保留的,以至于虛擬機認(rèn)為子進程中還是由很多線程的心剥。所以在子進程中執(zhí)行 SuspendAll
觸發(fā)暫停是永遠無法等到其他線程執(zhí)行暫停后的返回結(jié)果的邦尊。
經(jīng)過仔細分析 SuspendAll 過程背桐,我們發(fā)現(xiàn)可以先在主進程中執(zhí)行 SuspendAll 方法,使 ThreadList
中保存所有線程狀態(tài)為 suspend蝉揍,之后再fork链峭,這樣子進程共享父進程的 ThreadList 全局變量
_list,可以欺騙虛擬機疑苫,使其認(rèn)為所有的線程完成了暫停操作熏版,接下來就可以在子線程執(zhí)行 hprof.dump 操作了纷责。而主進程在 fork
之后調(diào)用 ResumAll 恢復(fù)運行捍掺。
fork dump流程圖dump hprof blocking 時間對比
hprof 文件通常比較到,文件的體積和手機最大堆內(nèi)存呈正相關(guān)再膳。分析 OOM 時遇到 500M 以上的 hprof
文件也不稀奇挺勿。文件的大小和 dump 成功率、dump
性能喂柒、上傳成功率均呈負相關(guān)不瓶。且大文件額外浪費用戶的磁盤空間和上傳到服務(wù)端的流量。因此需要對 hprof 文件進行裁剪灾杰,只保留分析 OOM
必須的數(shù)據(jù)蚊丐,另外,裁剪還有對用戶數(shù)據(jù)脫敏的好處艳吠,只上傳內(nèi)存中類域?qū)ο蟮慕M織結(jié)構(gòu)麦备,并不上傳真是的業(yè)務(wù)數(shù)據(jù)(諸如字符串、byte
數(shù)組等含有具體數(shù)據(jù)的內(nèi)容)昭娩,保護了用戶的隱私凛篙。
Hprof 文件中的內(nèi)容都是些什么?數(shù)據(jù)如何組織的栏渺?哪些可以裁剪呛梆?
內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)和 hprof 文件二進制協(xié)議的映射關(guān)系?
如何裁剪
hprof 的數(shù)據(jù)結(jié)構(gòu)
查閱 openJDK 官方文檔和 AOSP 源碼可知(更多 Hprof 文件信息見HPROF Agent)
Hprof 文件按 byte by byte 順序存儲磕诊,u1填物、u2、u4 分別代表 1byte 霎终、2byte滞磺、4byte
Hprof 的核心數(shù)據(jù)結(jié)構(gòu)物 Header 和 Recod,Header 記錄元數(shù)據(jù)神僵,Record 分很多條目雁刷,每一條有一個單獨的 TAG 表示(下圖來自 ART 源碼)
我們主要關(guān)注 Record 中的 HEAP_DUMP 類型,其中分為五個子類保礼,分別為GC ROOT沛励、CLASS DUMP责语、INSTANCE DUMP、OBJECT ARRAY DUMP 目派、PRIMITIVE ARRAY DUMP坤候。
下圖為 PRIMITIVE ARRAY DUMP(基本類型數(shù)組)的數(shù)據(jù)結(jié)構(gòu),內(nèi)存中絕大部分?jǐn)?shù)據(jù)是 PRIMITIVE ARRAY
DUMP,通常占據(jù) 80% 以上企蹭,而我們 OOM 分析只關(guān)心對象的引用關(guān)系和對象大小白筹,其他部分我們并不關(guān)心,因此這部分是我們裁剪 Hprof
的主要突破口谅摄。
通過 Art hprof.cc源碼可以看到Android 對 TAG 類型做了拓展徒河,增加了一些 GC ROOT 類型
Android 對 Java堆進行了更細致的劃分(用 HEAP_DUMP_INFO 表示具體劃分情況見下圖)。我們只關(guān)心
HPROF_HEAP_APP 即可送漠,其余的部分都可以裁剪掉顽照,這是因為其他部位都是 Android 內(nèi)存所有 App 共享的不參與
GC。這里可以參考 Android Studio 中 Memory Profile 的處理闽寡。
接下來討論的是如何實現(xiàn)裁剪代兵,裁剪的方案有兩個:
在 dump 完成后的 hprof 文件上做裁剪
在 dump 過程中做實時裁剪
為了較少不必要的 IO 操作和資源消耗,快手選擇了第二種爷狈。
順著 TAG 找到了 HEAP_DUMP 的寫入點植影,PS :下圖可以清楚的看到 Recod的寫入過程:
使用StartNewRecord() 寫入開始 TAG 和當(dāng)前時間戳
使用 StartNewRecord 寫入結(jié)束 TAG 和事件戳
調(diào)用 Enreccord() 收尾。
根據(jù)快手 Anroid 專家的意思寫入的過程也是使用AddU1/U4/U8 方法將數(shù)據(jù)寫入 Buffer涎永。但是筆者對 C/C++ 不了解思币,沒看懂源碼是如何將 hprof 文件到寫入 buffer 中的,
在寫入時先會執(zhí)行StartNewRecord() 方法,然后通過 AddU1/U4 寫入 buffer此時可以發(fā)現(xiàn) Record 數(shù)據(jù)的都是通過 AddU1/U4/U8 方法寫入 Buffer 的
我們在 EndRecord 中順著 HandleEndRecord 的蔓摸到了最終寫入磁盤的方法:WriteFully土辩。
顯而易見此時有兩個點可提我們 hook:
AddUx 方法
WriteFully 方法
理論上講 AddUx 方法性能最好支救,可以較少寫入 buffer 時候的內(nèi)存開銷。但是 AddUx 的調(diào)用比較復(fù)雜拷淘,而且他是 ART
內(nèi)部代碼各墨,很可能會被不同手機廠商修改出現(xiàn)兼容性的問題。而 write 是一個外部調(diào)用且只在 Record 寫入文件的時候調(diào)用一次启涯,從 hook
原理上來講贬堵,hook 外部調(diào)用的 PLT/GOThook 也比 hook 內(nèi)部調(diào)用的 inline hook 要穩(wěn)定的多(PS:好吧,我對
hook 這塊一無所知结洼,一臉懵逼)黎做。因此快手最終選擇了方法二。
為什么 LeakCanary 需要主動觸發(fā) GC 呢松忍?LeakCanary 監(jiān)控泄漏利用了弱引用的特征蒸殿,為 Activity
對象創(chuàng)建弱引用,當(dāng) Activity 變?yōu)槿蹩蛇_(沒有強引用)的時候,弱引用將會加入弱引用隊列中宏所,但是 GC
的觸發(fā)時機不可知酥艳,所以不能僅僅依靠弱引用隊列是否不為空就確定存在內(nèi)存泄漏。在通過主動 GC
的方式可以使垃圾回收器進行一次掃描標(biāo)記從而確定是否存在內(nèi)存泄漏(確認(rèn)的方式還是看弱引用隊列中是否存在此 Activity 的弱引用)爬骤。
但是 GC 是 Stop-The-World 的充石,頻繁的 GC 會造成用戶感知的明顯卡頓。為了解決這一方案快手霞玄、美團都使用了閾值判定的方式設(shè)計了無性能損耗的內(nèi)存監(jiān)控策略骤铃。其中快手的策略大致如下:
Java 堆、線程數(shù)量坷剧、文件描述符數(shù)量突破閾值觸發(fā)采集
Java 堆上漲速度突破閾值觸發(fā)采集惰爬。
發(fā)生 OOM 是如果策略 1、2 未命中觸發(fā)采集听隐。
泄漏判定延到解析時补鼻。
改成閾值監(jiān)控除了性能外哄啄,還有其他明顯的好處 :OOM 的來源一般有兩種
是內(nèi)存的峰值過高雅任。例如特大圖片占用內(nèi)存過大、不合理的 Bitmap 緩存池咨跌。
持續(xù)的內(nèi)存泄漏沪么,內(nèi)存水位線逐漸上漲。
LeakCanary 的弱引用策略僅能解決內(nèi)存泄漏問題锌半,而且需要手動埋點(PS:沒試過禽车,怎么埋點呢?)而通過閾值監(jiān)控的方式可以同時解決內(nèi)存泄漏和峰值的問題刊殉,也不需要手動埋點殉摔,甚至還能解決頻繁對象的引發(fā)的內(nèi)存抖動問題。
那么如何判定對象泄漏呢记焊?以 Activity 為例逸月,其實并不需要在運行時判定其是否內(nèi)存泄漏,Activtu 內(nèi)部有一個成員變量
mDestroyed遍膜,在 onDestroy 時會被置為 true碗硬,只要解析時發(fā)現(xiàn) 可達且 mDestroyed 為 true 的
Activity 即判定為泄漏。其他類型的對象可以以此類推瓢颅,根據(jù)其特點設(shè)計恩尾。
三板斧的最后一斧就是鏡像分析了,其基本思路是模仿 GC 算法挽懦,對我們關(guān)注的對象(泄漏翰意、大對象)進行科可達性分析,找出 GC Root 和引用鏈,再結(jié)合代碼分析冀偶。
暴力解析引用關(guān)系樹的 CPU 和內(nèi)存消耗都是很高的虎囚,即使在獨立進程解析,很容易觸發(fā) OOM 或者被系統(tǒng)強殺蔫磨,成功率非常低淘讥。因此需要對解析算法進行優(yōu)化。
對所有對象進行可達性分析性能開銷是巨大的堤如,且沒有重點也不利于問題解決蒲列,回顧前文,如果我們只關(guān)注泄漏對象和不合理使用的大對象搀罢,那么第一步就可以對引用關(guān)系樹盡可能的進行剪枝:
泄漏對象最典型的 Activity 和 Fragment蝗岖,有著明顯的生命周期特征且持有大量資源,除此外我們還對自定義核心組件做了泄漏判定榔至,比如 Presenter抵赢。
常見的大對象有 Bitmap、Array唧取、TextTure 等铅鲤,Bitamp/TextTure 的數(shù)量、寬高枫弟,Array 長度等等可以結(jié)合業(yè)務(wù)數(shù)據(jù)比如屏幕大小邢享、View Size 等進行判定。
有些對象既是泄漏也是大對象淡诗,關(guān)鍵對象的選取策略需要各 App 結(jié)合實際情況來靈活定制骇塘。
LeakCanary 早起使用的解析引擎是 HAHA 。在 HAHA1 使用了 MAT 的分析組件到了 HAHA2 改為 Android
Stuido Perlib 組件韩容。但是這兩個組件都是基于 PC
端設(shè)計的且表現(xiàn)優(yōu)秀款违,但是在移植到資源受限的手機端后,性能就急劇下降群凶,解析過程很容易出現(xiàn)第二次 OOM插爹,且解析極慢,成功率低座掘。
隨著 LeakCanary 2.0 版本的發(fā)布递惋,其研發(fā)團隊退出了新一代 hporf 分析引擎 shark。其宣稱 Shark 相比
HAHA 內(nèi)存峰值降低10倍溢陪,解析速度提升 6倍萍虽。快手團隊試驗后發(fā)現(xiàn)在較小的 hprof 文件下 Shark
的表現(xiàn)和宣稱的性能一致形真。但是在分析包含百萬級對象的 hprof 是性能解決下降杉编,分析耗時突破 10 分鐘超全。
沒有懶加載, hprof 內(nèi)容會全部 load 到內(nèi)存中邓馒。
Domainitor tree 和 retained size 全量計算嘶朱,而我們只關(guān)心關(guān)鍵對象
頻發(fā)觸發(fā) GC : 這是因為 Java 的集合類沒有針對計算密集型任務(wù)做優(yōu)化,含有大量用于的拆箱光酣、裝箱疏遏、擴容拷貝等操作,大量的對象創(chuàng)建頻發(fā)的 GC 救军,GC 反過來進一步降低對象分配速度财异,陷入惡性循環(huán)。
Shark 的核心思想就是懶加載和數(shù)據(jù)結(jié)構(gòu)優(yōu)化:
索引唱遭。Sark低內(nèi)存開銷的本質(zhì)就是通過索引做到了懶加載戳寸,遍歷 hprof 時存儲對象在 hprof 中的文職,并建立做索引方便按需解析
數(shù)據(jù)結(jié)構(gòu)上做了深度優(yōu)化拷泽,主要使用了更高效的 map疫鹊,一是對于 key 和 value 都是基本類型的 hppc,二是對于 value 不是基本類型的司致,使用 SotredBytesMap 村粗內(nèi)容拆吆。
Hppc
是 High Performance Primitive Collection 的縮寫,Shark 使用了 Kotlin
將其進行了了重寫蚌吸,hppc 只支持基本類型协屡,我們知道 kotlin
只有基本類型沒有裝箱類型算芯,所以沒有了拆箱和裝箱的性能損耗伦糯,相關(guān)集合操作也做了大量優(yōu)化酗洒。
索引對應(yīng)前文提到的 hprof Record 中的 HEAP DUMP 類型削祈。
包含實例索引尤莺,類索引曹铃,字符串索引妓局,類名索引萌焰,數(shù)組索引哺眯。
我們的目標(biāo)是打造線上內(nèi)存分析工具,所以和 LeakCanary 還是存在挺大差異的:
LeakCanary 中 Shark 只用于分析單一內(nèi)存泄漏對象的引用鏈扒俯,而線上 OOM 分析工具要分析的是大量對象的引用鏈奶卓。
LeakCanary 對于結(jié)果的要求非常準(zhǔn)確,而我們的是線上大數(shù)據(jù)分析撼玄,允許丟棄個別對象的引用鏈夺姑,犧牲一些覆蓋率來大幅度提高穩(wěn)定性。
LeakCanary 為了讓分析日志更加詳盡掌猛,對于鏡像中的對象所有字段都會進行分析盏浙,而我們只關(guān)心引用鏈并不關(guān)心基礎(chǔ)類型的值。所以此步驟可以省略。
對 GC Root 剪枝废膘,可達性分析是從 GC Root 自頂向下 BFS竹海,如 JavaFrame MonitorUsed 等 GC Root 都可以直接剪枝。
基本類型和基本類型數(shù)組不搜索丐黄、不解析斋配;同類對象超過閾值時不再搜索。
增加預(yù)處理灌闺,緩存每個類遞歸調(diào)用的結(jié)果许起,減少重復(fù)計算。
將 object ID 的 類型從 long 改為 int菩鲜,Android 虛擬機的 object ID 大小只有 32 位园细,目前shark 里使用的都是 long 來存儲的, OOM 時百萬級對象的情況下接校,可以節(jié)省10M 內(nèi)存猛频。
經(jīng)過上面的優(yōu)化、將解析時間在 Shark 的基礎(chǔ)上優(yōu)化了2倍以上蛛勉,內(nèi)存峰值控制在 100M 以內(nèi)鹿寻。
此部分就沒啥好說的了,就只能看看人家大廠是如何做的诽凌。
基礎(chǔ)知識很重要
源碼閱讀能力很重要
遇見問題要抓住頭部問題毡熏,例如如果用戶入口頁、長時間駐留頁發(fā)生內(nèi)存泄漏影響是最大的侣诵,一旦出現(xiàn)問題穩(wěn)定性會大幅度降低痢法。需要第一時間解決。
大需求何如前一定要做對比灰度杜顺,OOM 問題很難定位到是哪個需求引入的财搁,如果在需求何入前通過對比灰度數(shù)據(jù),可以精準(zhǔn)的預(yù)估出實際上線后對大盤的影響躬络。
推薦打包為 64 為的 App 尖奔,32位的地址空間僅有4G,內(nèi)核固定使用1G穷当,進程啟動的時候就會劃分出去 2 ~3 G的虛擬地址范圍提茁,很容易出現(xiàn)手機配置越高越容易出現(xiàn) OOM 的現(xiàn)象。