本文主要有以下三部分內(nèi)容:
第一部分:簡(jiǎn)單介紹開(kāi)發(fā)者指南上內(nèi)存相關(guān)的文章。
第二部分:總結(jié)移動(dòng)App性能評(píng)測(cè)與優(yōu)化內(nèi)存篇相關(guān)內(nèi)容。
第三部分:Android內(nèi)存相關(guān)好文章
開(kāi)發(fā)者指南內(nèi)存篇
以下是官方文檔內(nèi)存篇相關(guān)內(nèi)容:
管理應(yīng)用內(nèi)存
內(nèi)存管理預(yù)覽
調(diào)查 RAM 使用情況
使用 Memory Profiler 查看 Java 堆和內(nèi)存分配
dumpsys meminfo
管理應(yīng)用內(nèi)存
主要內(nèi)容有:
(1)監(jiān)控可用內(nèi)存及內(nèi)存使用:在手機(jī)有內(nèi)存壓力時(shí),系統(tǒng)會(huì)發(fā)廣播進(jìn)行提示,應(yīng)用根據(jù)這些
信息對(duì)內(nèi)存使用作出恰當(dāng)?shù)奶幚恚{(diào)用getMemoryInfo()方法去查詢當(dāng)前設(shè)備的可用內(nèi)存堆內(nèi)存空間。
(2)從代碼角度優(yōu)化內(nèi)存:節(jié)省使用Service,除非sercie要去執(zhí)行一個(gè)任務(wù),否則不應(yīng)該一直在后臺(tái)駐留,
使用Android框架提供的優(yōu)化過(guò)的數(shù)據(jù)結(jié)構(gòu),如ArrayMap替代Haskmap等等;代碼抽象會(huì)
代碼嚴(yán)重的開(kāi)銷(xiāo),所以盡量少使用代碼抽象,使用nano protobufs進(jìn)行序列化,避免內(nèi)存抖動(dòng),因?yàn)閮?nèi)存抖動(dòng)會(huì)
觸發(fā)更多的GC,影響手機(jī)性能,
(3)移除內(nèi)存敏感的資源和庫(kù):減少APK大小,在需要使用注解時(shí),考慮使用Dragger,Dragger不會(huì)增加不必要內(nèi)存使用。
謹(jǐn)慎使用外部庫(kù),我們可能只需要外部庫(kù)一個(gè)很小的功能,如果引入外部庫(kù)可能會(huì)帶來(lái)更大的內(nèi)存開(kāi)銷(xiāo)。
內(nèi)存管理預(yù)覽
主要內(nèi)容有:
(1)垃圾回收:垃圾回收的兩個(gè)目標(biāo),首先是找到不再使用的對(duì)象,釋放不再使用的對(duì)象,
Android里面是一個(gè)三級(jí)Generation的內(nèi)存模型,不同的Generation采用不同的垃圾回收方式,GC所占用的時(shí)間和它是哪一個(gè)Generation也有關(guān)系。
(2)共享內(nèi)存:不同進(jìn)程之間可以共享框架代碼和系統(tǒng)資源,應(yīng)用和屏幕合成者通過(guò)匿名共享內(nèi)存共享surface數(shù)據(jù)。
通過(guò)共享內(nèi)存可以節(jié)省內(nèi)存資源。
(3)分配和釋放內(nèi)存:應(yīng)用內(nèi)存可以根據(jù)自己的需求不變變大,但是不能超多每個(gè)應(yīng)用的最大限制。
(4)限制應(yīng)用內(nèi)存:如果應(yīng)用申請(qǐng)的內(nèi)存超過(guò)自己最大可使用內(nèi)存,這時(shí)候就會(huì)有內(nèi)存溢出,應(yīng)用可以通過(guò)getMemoryClass()
方法獲取應(yīng)用最大可使用堆的大小。
(5)切換應(yīng)用:當(dāng)應(yīng)用在前后臺(tái)切換的時(shí)候,如果遇到內(nèi)存緊張,系統(tǒng)會(huì)根據(jù)LRU緩存去殺掉部分進(jìn)程,應(yīng)用在后臺(tái)時(shí)使用的
內(nèi)存越小,就越不容易被殺掉,這樣應(yīng)用在切換到前臺(tái)時(shí)更快。
調(diào)查 RAM 使用情況
即使您在開(kāi)發(fā)過(guò)程中遵循了管理應(yīng)用的內(nèi)存的所有最佳做法,您仍然可能泄漏對(duì)象或引入其他內(nèi)存錯(cuò)誤。唯一能夠確定您的應(yīng)用盡可能少地使用內(nèi)存的方法是,利用本文介紹的工具分析應(yīng)用的內(nèi)存使用情況。主要內(nèi)容有:
(1)解讀Dalvik、ART虛擬機(jī)GC日志,主要有GC原因、垃圾回收名稱(chēng)、釋放大小,暫停時(shí)間等等;
(2)捕捉堆轉(zhuǎn)儲(chǔ):堆轉(zhuǎn)儲(chǔ)是應(yīng)用堆中所有對(duì)象的快照。堆轉(zhuǎn)儲(chǔ)以一種名稱(chēng)為 HPROF 的二進(jìn)制格式存儲(chǔ),您可以將其上傳到分析工具中。
應(yīng)用的堆轉(zhuǎn)儲(chǔ)包含應(yīng)用堆整體狀態(tài)的相關(guān)信息,以便您能夠跟蹤在查看堆更新時(shí)發(fā)現(xiàn)的問(wèn)題。
(3)查看堆更新:使用 Android Monitor 在您與應(yīng)用交互時(shí)查看應(yīng)用堆的實(shí)時(shí)更新。實(shí)時(shí)更新提供了為不同應(yīng)用操作分配的內(nèi)存量的相關(guān)信息。
您可以利用此信息確定是否任何操作占用了過(guò)多內(nèi)存以及是否需要調(diào)整以減少占用的內(nèi)存量。
(4)分析堆轉(zhuǎn)儲(chǔ):堆轉(zhuǎn)儲(chǔ)使用與 Java HPROF 工具中類(lèi)似但不相同的格式提供。Android 堆轉(zhuǎn)儲(chǔ)的主要區(qū)別是在 Zygote 進(jìn)程中進(jìn)行了大量的分配。
因?yàn)?Zygote 分配在所有應(yīng)用進(jìn)程之間分享,所以它們對(duì)您自己的堆分析影響不太大。
(5)跟蹤內(nèi)存分配:具體內(nèi)容可參考:使用 Memory Profiler 查看 Java 堆和內(nèi)存分配,Android Profiler是測(cè)量應(yīng)用性能好工具主要有以下內(nèi)容:使用 CPU Profiler 檢查 CPU Activity 和函數(shù)跟蹤
使用 Memory Profiler 查看 Java 堆和內(nèi)存分配
利用 Network Profiler 檢查網(wǎng)絡(luò)流量
(6)查看整體內(nèi)存分配:使用 adb shell dumpsys meminfo <package_name|pid> [-d] 命令觀察應(yīng)用內(nèi)存在不同類(lèi)型的 RAM 分配之間的劃分情況,-d 標(biāo)志會(huì)打印與 Dalvik 和 ART 內(nèi)存使用情況相關(guān)的更多信息,輸出列出了應(yīng)用的所有當(dāng)前分配,單位為千字節(jié)。我們應(yīng)該熟悉不同內(nèi)存類(lèi)型的分配,詳細(xì)內(nèi)容請(qǐng)閱讀官方文檔。
(7)觸發(fā)內(nèi)存泄漏:內(nèi)存泄露越小,就需要運(yùn)行更長(zhǎng)的時(shí)間發(fā)現(xiàn)泄露點(diǎn),可以通過(guò)橫豎屏切換,應(yīng)用切換來(lái)觸發(fā)內(nèi)存泄露。
使用 Memory Profiler 查看 Java 堆和內(nèi)存分配
主要內(nèi)容有:為什么分析應(yīng)用內(nèi)存、Memory Profiler概覽、如何計(jì)算內(nèi)存、查看內(nèi)存分配、捕獲堆轉(zhuǎn)儲(chǔ)、將對(duì)轉(zhuǎn)儲(chǔ)另存為Hprof、分析內(nèi)存技巧等,具體內(nèi)容請(qǐng)直接參閱官方文檔。
dumpsys meminfo
dumpsys meminfo具體內(nèi)容請(qǐng)閱讀 dumpsys,該內(nèi)容和調(diào)查 RAM 使用情況中的查看整體內(nèi)存分配部分基本一致。
以上就是官方文檔關(guān)于內(nèi)存部分的簡(jiǎn)單介紹,更具體的內(nèi)容請(qǐng)閱讀官方文檔。
移動(dòng)App性能評(píng)測(cè)與優(yōu)化內(nèi)存篇相關(guān)內(nèi)容
主要將自己認(rèn)為重要的內(nèi)容記錄下來(lái),部分內(nèi)容會(huì)根據(jù)最新的Android版本加入一些自己的理解。雖然Android一直在不停的演進(jìn),書(shū)中描述的部分問(wèn)題在最新Android手機(jī)上已經(jīng)不存在,但是書(shū)中描述的一些分析方法和經(jīng)驗(yàn)還是很值得學(xué)習(xí)的。
MAT工具使用技巧
MAT打開(kāi)hprof文件之后,使用Top Consumers和Component Report功能,使用這些功能能快速定位大塊內(nèi)存消耗,由于虛擬機(jī)不會(huì)區(qū)分系統(tǒng)資料和應(yīng)用自身的對(duì)象,可以采用兩種方式來(lái)區(qū)分系統(tǒng)框架資源和應(yīng)用自身對(duì)象
方法一:hprof-conv轉(zhuǎn)換時(shí)添加“-z”參數(shù);
方法二:hprof已經(jīng)轉(zhuǎn)換過(guò)了,在數(shù)據(jù)中尋找應(yīng)用的Application類(lèi)對(duì)象,可以使用OQL語(yǔ)句查詢應(yīng)用自身對(duì)象;
使用-z和OQL查詢語(yǔ)句得到的對(duì)象集合就是應(yīng)用代碼分配的部分。這樣就剔除了系統(tǒng)資源的影響。
Dilvik Heap常見(jiàn)問(wèn)題及相關(guān)分析工具
(1)功能反復(fù)執(zhí)行,Heap一直在持續(xù)增長(zhǎng),這種情況通常出現(xiàn)內(nèi)存泄露,適合用LeakCanary等泄露工具進(jìn)行白盒測(cè)試分析;
(2)代碼執(zhí)行時(shí)出現(xiàn)頻繁的GC,Heap Alloc內(nèi)存大幅波動(dòng),通常是分配了許多臨時(shí)變量和數(shù)組,雖然又被回收,適合使用Heap Viewer/Allocation Tracker等工具來(lái)查看具體分配的對(duì)象;
(3)每次啟動(dòng)應(yīng)用之后,Heap內(nèi)存相對(duì)以前版本穩(wěn)定增長(zhǎng),可能是由于新功能機(jī)代碼改動(dòng)引入的固定內(nèi)存增長(zhǎng),獲取Heap Dump進(jìn)行多版本使用前后對(duì)比來(lái)查找增長(zhǎng)原因。
(4)Heap Alloc變化不大,但進(jìn)程Dalvik Heap Pss內(nèi)存明顯增加,是由于分配了大量小對(duì)象造成內(nèi)存碎片的原因。
新問(wèn)題
新功能可能會(huì)分配幾萬(wàn)到幾十萬(wàn)字節(jié)的內(nèi)存,實(shí)際增加的內(nèi)存為2MB,但是Dalvik heap內(nèi)存并沒(méi)有增加太多(200kb),說(shuō)明問(wèn)題不在Dalvik里就能解決,需要我們進(jìn)一步深挖,Heap內(nèi)存并不是應(yīng)用的全部,可以通過(guò)dumpsys查看應(yīng)用整個(gè)進(jìn)程的使用量,以及各部分的使用量,最后發(fā)現(xiàn)Dalvik heap Pss部分增加比較多。最常用觀察進(jìn)程內(nèi)存的方法 adb shell dumpsys meminfo packge name| pid
上述描述的問(wèn)題就是Dalvik heap pss內(nèi)存增加了2M,Dalvik heap Alloc值增長(zhǎng)了273kb,但Dalvik heap Free也能看出大部分增長(zhǎng)的內(nèi)存是處于空閑狀態(tài)的。各種怪異的問(wèn)題,常用的方法找不出原因,說(shuō)明有更深層次的原因,Java代碼的內(nèi)存分配和釋放是由虛擬機(jī)管理的,我們需要通過(guò)虛擬機(jī)機(jī)制來(lái)探索內(nèi)存增長(zhǎng)的原因。
Dalvik heap內(nèi)部機(jī)制
(1)為什么DVM占用內(nèi)存不釋放,需要于都DVM內(nèi)存分配代碼(位于dalvik/vm/alloc下),目前ART虛擬機(jī)已經(jīng)取代了DVM,具體代碼位置沒(méi)有看過(guò)。
(2)新建對(duì)象之后,由于要向?qū)?yīng)的地址寫(xiě)入數(shù)據(jù),內(nèi)核開(kāi)始真正分配該地址對(duì)應(yīng)的4KB物理內(nèi)存頁(yè)面。代碼在Alloc.cpp中。
(3)運(yùn)行一段時(shí)間之后開(kāi)始GC,GC時(shí)可能會(huì)進(jìn)行trim,即將空閑的物理頁(yè)面釋放回系統(tǒng),表現(xiàn)為private dirty/pss下降,相關(guān)代碼在HeapSource.cpp中。
問(wèn)題所在以及優(yōu)化
(1)在了解DVM分配和釋放內(nèi)存的機(jī)制之后,根據(jù)dumpsys觀察到的現(xiàn)象,猜測(cè)可能是頁(yè)面利用率的問(wèn)題,如在GC之后,大部分對(duì)象被釋放,少部分留下來(lái),導(dǎo)致整頁(yè)的4KB內(nèi)存可能只有一個(gè)小對(duì)象,但統(tǒng)計(jì)的時(shí)候是按4KB來(lái)計(jì)算。
(2)將MAT中的數(shù)據(jù)導(dǎo)出為csv格式,然后按也頁(yè)面進(jìn)行統(tǒng)計(jì),可以查看也頁(yè)面利用率統(tǒng)計(jì)結(jié)果圖,利用率低的頁(yè)面增加說(shuō)明小對(duì)象碎片數(shù)量增加。
(3)取出步驟2中使用不滿2KB的頁(yè)面的內(nèi)存塊地址,重新導(dǎo)入MAT得到對(duì)象列表,基本可以看出那些對(duì)象造成了內(nèi)存的碎片化。
(4)問(wèn)題基本過(guò)程還原:生成對(duì)象過(guò)程需要很多臨時(shí)變量,批量生成過(guò)程中還有空閑內(nèi)存,虛擬機(jī)沒(méi)有垃圾回收,完成后進(jìn)行垃圾回收,清楚了所有的臨時(shí)變量,留下碎片化內(nèi)存,造成碎片化類(lèi)似代碼如下:
private Object result[] = new Object[NUM];
private Object test[] = new Object[NUM];
void test(){
for(int i = 0; i <NUM ; i ++ ){
byte[] tmp = new byte[NUM];
result[i] = new byte[NUM];
test[i] = new byte[NUM];
}
}
執(zhí)行以上代碼之后通過(guò)MAT查看數(shù)組每個(gè)成員的內(nèi)存地址,發(fā)現(xiàn)都是不連續(xù)的,這就到消耗很多的物理頁(yè)面,增加Heap Free。造成例子中的問(wèn)題(書(shū)中沒(méi)有描述具體如何操作,我自己也沒(méi)有嘗試,感興趣的可以試驗(yàn)一下,但是該問(wèn)題在Android使用ART虛擬機(jī)之后就不會(huì)存在碎片化問(wèn)題)。
總結(jié)
(1)MAT是探索java堆并發(fā)現(xiàn)問(wèn)題的好工具,能快速發(fā)現(xiàn)常見(jiàn)圖片和大數(shù)組問(wèn)題,但是MAT不是萬(wàn)能的,比如該問(wèn)題隱藏在對(duì)象地址中。內(nèi)存分配的最小單位是頁(yè)面,大小通常為4KB;盡量不要在循環(huán)中創(chuàng)建很多臨時(shí)變量,可以將大型循環(huán)拆開(kāi)、分段、按需執(zhí)行;
(2)在JVM中,虛擬機(jī)借助標(biāo)記整理算法將散布的內(nèi)存移動(dòng)到一起,這樣就不存在頁(yè)面利用率的問(wèn)題,但是在DVM由于使用Mark-Sweep標(biāo)記清除算法,該算法不能移動(dòng)對(duì)象,即沒(méi)有內(nèi)存整理,這樣就導(dǎo)致了內(nèi)存碎片問(wèn)題,導(dǎo)致以上問(wèn)題的產(chǎn)生。目前Android使用ART虛擬機(jī)取代DVM,ART使用了標(biāo)記整理算法進(jìn)行內(nèi)存回收,所以使用ART虛擬機(jī)的Android系統(tǒng)就不存在內(nèi)存碎片問(wèn)題,DVM與ART虛擬機(jī)區(qū)別可參考JVM、DVM以及ART虛擬機(jī)簡(jiǎn)介
內(nèi)存原理
內(nèi)存除了Dalvik Heap pss以外還有其它許多消耗內(nèi)存的部分,對(duì)Dalvik heap pss優(yōu)化后,可能會(huì)發(fā)現(xiàn)Delvik other和Mmap在內(nèi)存中的比重加大,我們需要繼續(xù)尋找辦法對(duì)在其他部分內(nèi)存進(jìn)行優(yōu)化,由于對(duì)這部分不熟悉,我們需要先去了解背后的原理,才能有針對(duì)性的去研究如何優(yōu)化這部分內(nèi)存。
從物理內(nèi)存到應(yīng)用,我們首先要了解系統(tǒng)的內(nèi)存機(jī)制,搞清楚屋里內(nèi)存如何被分配到各個(gè)進(jìn)程,以及共享內(nèi)存的機(jī)制,這些機(jī)制對(duì)內(nèi)存優(yōu)化有很大的幫助,根據(jù)Google提供的Android架構(gòu)圖可以看到Android是基于Linux內(nèi)核的,因此底層內(nèi)存分配和共享機(jī)制與Linux基本相同,由于Android是為移動(dòng)設(shè)備設(shè)計(jì)的,Android擴(kuò)充了許多內(nèi)核機(jī)制和實(shí)現(xiàn)。對(duì)內(nèi)存影響較大的是Ashmem和Binder機(jī)制,在Ashmem和COW機(jī)制基礎(chǔ)上,Android進(jìn)程最明顯的內(nèi)存特征是與zygote共享內(nèi)存,為了加快啟動(dòng)速度及節(jié)約內(nèi)存,Android應(yīng)用進(jìn)程都是由zygote fork出來(lái),由于zygote已經(jīng)載入完成的Dalvik虛擬機(jī)和Android應(yīng)用框架的代碼,fork出來(lái)的進(jìn)程和zygote共享同一塊內(nèi)存,這樣就節(jié)約了每個(gè)進(jìn)程單獨(dú)載入的時(shí)間和內(nèi)存,應(yīng)用進(jìn)程只需要載人自己的Dalvik字節(jié)碼及資料就可以運(yùn)行。
一個(gè)運(yùn)行的Android應(yīng)用進(jìn)程會(huì)包含以下幾個(gè)部分:
(1)Dalvik虛擬機(jī)代碼(共享內(nèi)存)
(2)應(yīng)用框架代碼(共享內(nèi)存)
(3)應(yīng)用框架資源(共享內(nèi)存)
(4)應(yīng)用框架so庫(kù)(共享內(nèi)存)
(5)應(yīng)用的代碼(私有內(nèi)存)
(6)應(yīng)用的資源(私有內(nèi)存)
(7)應(yīng)用的so庫(kù)(私有內(nèi)存)
(8)堆內(nèi)存、其它部分(共享/私有)。
通過(guò)dumpsys meminfo可以觀察內(nèi)存值,它將不同額內(nèi)存消耗分類(lèi)統(tǒng)計(jì),通過(guò)閱讀和分析dumpsys meminfo的代碼(自己未閱讀),可以了解Android是如何劃分各部分內(nèi)存的,知道dumpsys是如何統(tǒng)計(jì)各部分內(nèi)存的。
Android底層預(yù)計(jì)Linux內(nèi)核,進(jìn)程內(nèi)存信息和Linux一致,Dalvik heap之外的信息都能夠從/proc/pid/smaps/中獲取。我們可以通過(guò) adb shell cat /proc/pid/smaps > smapsinfo.txt將smaps詳細(xì)信息重定向到文本文件中進(jìn)行查看。smaps中信息如下:
(1)/dev/ashmem/dalvik-heap和/dev/ashmem/dalvik-zygote歸為Dalvik-heap;
(2)其它以/dev/ashmem/dilvik-開(kāi)頭的內(nèi)存區(qū)域歸為Dalvik-other
(3)文件的mmap按已知的幾個(gè)擴(kuò)展名分類(lèi)
(4)其余歸為Other mmap;
由于Android已使用ART虛擬機(jī)代替Dalvik虛擬機(jī),暫時(shí)不知道最新的smaps信息如何對(duì)內(nèi)存進(jìn)行分類(lèi)。
zygote內(nèi)存共享機(jī)制
Pss進(jìn)程實(shí)際使用的物理內(nèi)存,是私有內(nèi)存加上按比例分配的各進(jìn)程共享內(nèi)存得到的值,共享內(nèi)存是zygote加載的Android框架部分,會(huì)被所有的進(jìn)程分享,Dalvik pps內(nèi)存=私有內(nèi)存+共享內(nèi)存/共享進(jìn)程數(shù),所以當(dāng)一個(gè)進(jìn)程結(jié)束后,它所占用的共享內(nèi)存就會(huì)被其它使用,該共享庫(kù)的進(jìn)程所分擔(dān),所以一個(gè)進(jìn)程結(jié)束,可能會(huì)導(dǎo)致其它進(jìn)程的Pss內(nèi)存增加。
優(yōu)化Dex相關(guān)內(nèi)存
隨著代碼功能的增加,代碼復(fù)雜度也在不斷變大,這時(shí)候會(huì)發(fā)現(xiàn)Dalvik heap和Dex mmap這兩部分消耗的內(nèi)存增大(占總內(nèi)存比例變大),Dalvik other存放的是類(lèi)的數(shù)據(jù)結(jié)構(gòu)及關(guān)系,Dex mmap是類(lèi)函數(shù)代碼和常量,通常優(yōu)化這部分內(nèi)存,需要從代碼出發(fā),但如果我們深入理解系統(tǒng),也能夠找到其它方法來(lái)降低這部分的內(nèi)存消耗,所以我們?cè)趦?yōu)化內(nèi)存時(shí),不應(yīng)該只優(yōu)化堆內(nèi)存,在我們搞定其它類(lèi)型內(nèi)存的含義以及原理之后,也是能夠?qū)ζ渌糠值膬?nèi)存進(jìn)行優(yōu)化。雖然最新的Android版本Dalvik Other占用的內(nèi)存雖然不大,但是這給優(yōu)化內(nèi)存提供了一種思路。簡(jiǎn)單一段代碼在一個(gè)空應(yīng)用執(zhí)行以后,可以看到對(duì)應(yīng)heap、other、dex mmap的內(nèi)存增長(zhǎng),heap增長(zhǎng)可以通過(guò)代碼邏輯分析出來(lái)這段代碼需要分配多少,也可以在mat中看到新建對(duì)象消耗的內(nèi)存,當(dāng)應(yīng)用使用完新創(chuàng)建的對(duì)象后,就會(huì)將heap內(nèi)存釋放,但是other和dex mmap不會(huì)被釋放。
一個(gè)類(lèi)的內(nèi)存消耗以及new一個(gè)對(duì)象的步驟
虛擬機(jī)在執(zhí)行這步時(shí)會(huì)做什么那?
第一步是loadClass操作,將類(lèi)信息從dex文件加載到內(nèi)存中;
(1)讀取.dex mmap中的class對(duì)應(yīng)的數(shù)據(jù);
(2)分配native-heap和dalvik-heap內(nèi)存創(chuàng)建class對(duì)象;
(3)分配dalvik-linearAlloc存放class數(shù)據(jù);
(4)分配dalvik-aux-structure存放class數(shù)據(jù);
第二步:new instance操作,創(chuàng)建對(duì)象實(shí)例:
(1)執(zhí)行dex mmap中的<clinit>和<init>代碼;
(2)分配dalvik-heap創(chuàng)建class對(duì)象實(shí)例;
如果對(duì)象引用了其它類(lèi)型,那還需要先按照同樣的邏輯創(chuàng)建被引用的class,在創(chuàng)建一個(gè)類(lèi)實(shí)例的每一步都需要消耗內(nèi)存,可以大概計(jì)算一下new操作需要消耗的內(nèi)存;根據(jù)虛擬機(jī)的代碼能夠得知class根據(jù)類(lèi)成員和函數(shù)數(shù)目分配linearAlloc和aux-structure的多少,以及class本身及函數(shù)需要的字節(jié)數(shù),我們?cè)俑鶕?jù)所以class總量進(jìn)行平均計(jì)算得到一組數(shù)據(jù):
第一步是loadClass操作,加載類(lèi)信息;
(1).dex mmap:載入一個(gè)類(lèi)需要先讀取259字節(jié)的mmap
(2)dalvik-linearAlloc:在linearAlloc區(qū)域分配437字節(jié),存放類(lèi)的靜態(tài)數(shù)據(jù);
(3)dalvik-aux-structure:在aux區(qū)域分配88字節(jié),存放各種指針
第二步:new instance操作,創(chuàng)建對(duì)象實(shí)例:
(1)dex mmap :為了執(zhí)行類(lèi)的構(gòu)造函數(shù),還需要讀取252字節(jié)mmap
(2)dalvik-heap:根據(jù)類(lèi)的具體內(nèi)容而變化。
由于內(nèi)存最小分配單位是頁(yè)面,同時(shí)內(nèi)存分配并不是連續(xù)分布,所以可能需要分配多個(gè)4KB頁(yè)面。
Dex mmap在Android應(yīng)用中作用是映射class.dex文件,Dilvik虛擬機(jī)需要從dex文件中加載類(lèi)信息、字符串常量,需要在調(diào)用函數(shù)時(shí)直接從mmap內(nèi)存中讀取函數(shù)代碼來(lái)執(zhí)行,所以該部分內(nèi)存是程序運(yùn)行必不可少的。
.dex文件將所有的class里邊所包含的信息全部整合在一個(gè)??梢允褂肁ndroid SDK提供的dexdump工具來(lái)觀察dex文件內(nèi)容,假設(shè)代碼里用到A1類(lèi)后,還用到B1、C1、D1類(lèi),如果能在dex文件中將A1、B1、C1、D1類(lèi)放在一起,虛擬機(jī)就只需要加載一個(gè)4KB的頁(yè)面,可以減少內(nèi)存使用。優(yōu)化思路就是調(diào)整dex文件中數(shù)據(jù)的順序,盡量將使用到的數(shù)據(jù)內(nèi)容排列在一起。Proguard工具能夠?qū)︻?lèi)名進(jìn)行修改,根據(jù)程序運(yùn)行的邏輯將那些會(huì)互相調(diào)用的類(lèi)改為同一個(gè)packag名,這樣就可以使他們的數(shù)據(jù)排列在一起。
小結(jié)
(1)優(yōu)化內(nèi)存時(shí),不只有堆內(nèi)存,還有其它許多類(lèi)型的內(nèi)存能夠進(jìn)行分析和優(yōu)化;
(2)dex文件有很多優(yōu)化空間,調(diào)整dex文件順序,可以節(jié)約mmap的內(nèi)存;
(3)引入sdk和調(diào)用新的系統(tǒng)API需要考慮成本,不成用的功能可能導(dǎo)致大量的內(nèi)存消耗,這時(shí)可以考慮多進(jìn)程方案,將影響內(nèi)存的操作放入到臨時(shí)進(jìn)程執(zhí)行。
總結(jié)
內(nèi)存主要組成:
(1)native heap:Native代碼分配的內(nèi)存,虛擬機(jī)和Android框架本身也會(huì)分配;
(2)Dalvik heap:Java代碼分配的對(duì)象;
(3)Dalvik Other:類(lèi)的數(shù)據(jù)結(jié)構(gòu)和索引;
(4)so mmap:Native代碼和常量
(5)dex mmap:Java代碼和常量;
內(nèi)存工具:
(1)Android Studio/Memory Monitor:觀察Dalvik內(nèi)存;
(2)dumpsys meminfo:觀察整體內(nèi)存;
(3)smaps:整體內(nèi)存的詳細(xì)組成;
(4)MAT:分析Java對(duì)并發(fā)現(xiàn)問(wèn)題好工具;
經(jīng)驗(yàn)總結(jié):
(1)內(nèi)存分配的最小單位是頁(yè)面,通常為4KB;
(2)碎片不僅僅是Dalvik內(nèi)存,還包括各種mmap可能產(chǎn)生的內(nèi)存碎片,在Android4.4引入ART虛擬機(jī)之后,ART虛擬機(jī)的垃圾收集器(MarkSweep+Semispace)會(huì)進(jìn)行碎片整理,所以就不存在碎片問(wèn)題;
性能優(yōu)化:
(1)盡量不要在循環(huán)中創(chuàng)建很多臨時(shí)變量,可能會(huì)觸發(fā)頻繁的GC,導(dǎo)致內(nèi)存抖動(dòng);
(2)性能優(yōu)化不只有堆內(nèi)存優(yōu)化,其它類(lèi)型的內(nèi)存,我們需要先了解其原理之后,就可以有針對(duì)性進(jìn)行分析和優(yōu)化;
Android內(nèi)存其它好文章
Android性能優(yōu)化-內(nèi)存泄漏(上)
Android性能優(yōu)化-內(nèi)存泄漏(下)
leakcanary源碼學(xué)習(xí)隨筆
Android 性能優(yōu)化的方方面面都在這兒-內(nèi)存優(yōu)化
Android性能優(yōu)化-方法區(qū)導(dǎo)致內(nèi)存問(wèn)題實(shí)例分析