本文主要有以下三部分內(nè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)好文章
開發(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)化過的數(shù)據(jù)結(jié)構(gòu)虚青,如ArrayMap替代Haskmap等等它呀;代碼抽象會(huì)
代碼嚴(yán)重的開銷,所以盡量少使用代碼抽象棒厘,使用nano protobufs進(jìn)行序列化纵穿,避免內(nèi)存抖動(dòng),因?yàn)閮?nèi)存抖動(dòng)會(huì)
觸發(fā)更多的GC奢人,影響手機(jī)性能谓媒,
(3)移除內(nèi)存敏感的資源和庫:減少APK大小,在需要使用注解時(shí)何乎,考慮使用Dragger句惯,Dragger不會(huì)增加不必要內(nèi)存使用。
謹(jǐn)慎使用外部庫支救,我們可能只需要外部庫一個(gè)很小的功能宗弯,如果引入外部庫可能會(huì)帶來更大的內(nèi)存開銷。
內(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)用和屏幕合成者通過匿名共享內(nèi)存共享surface數(shù)據(jù)详恼。
通過共享內(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)存超過自己最大可使用內(nèi)存挽铁,這時(shí)候就會(huì)有內(nèi)存溢出,應(yīng)用可以通過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 使用情況
即使您在開發(fā)過程中遵循了管理應(yīng)用的內(nèi)存的所有最佳做法浓镜,您仍然可能泄漏對(duì)象或引入其他內(nèi)存錯(cuò)誤。唯一能夠確定您的應(yīng)用盡可能少地使用內(nèi)存的方法是劲厌,利用本文介紹的工具分析應(yīng)用的內(nèi)存使用情況膛薛。主要內(nèi)容有:
(1)解讀Dalvik、ART虛擬機(jī)GC日志补鼻,主要有GC原因相叁、垃圾回收名稱、釋放大小辽幌,暫停時(shí)間等等增淹;
(2)捕捉堆轉(zhuǎn)儲(chǔ):堆轉(zhuǎn)儲(chǔ)是應(yīng)用堆中所有對(duì)象的快照。堆轉(zhuǎn)儲(chǔ)以一種名稱為 HPROF 的二進(jìn)制格式存儲(chǔ)乌企,您可以將其上傳到分析工具中虑润。
應(yīng)用的堆轉(zhuǎn)儲(chǔ)包含應(yīng)用堆整體狀態(tài)的相關(guān)信息,以便您能夠跟蹤在查看堆更新時(shí)發(fā)現(xiàn)的問題加酵。
(3)查看堆更新:使用 Android Monitor 在您與應(yīng)用交互時(shí)查看應(yīng)用堆的實(shí)時(shí)更新拳喻。實(shí)時(shí)更新提供了為不同應(yīng)用操作分配的內(nèi)存量的相關(guān)信息。
您可以利用此信息確定是否任何操作占用了過多內(nèi)存以及是否需要調(diào)整以減少占用的內(nèi)存量猪腕。
(4)分析堆轉(zhuǎn)儲(chǔ):堆轉(zhuǎn)儲(chǔ)使用與 Java HPROF 工具中類似但不相同的格式提供冗澈。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)存在不同類型的 RAM 分配之間的劃分情況,-d 標(biāo)志會(huì)打印與 Dalvik 和 ART 內(nèi)存使用情況相關(guān)的更多信息捌归,輸出列出了應(yīng)用的所有當(dāng)前分配肛响,單位為千字節(jié)。我們應(yīng)該熟悉不同內(nèi)存類型的分配惜索,詳細(xì)內(nèi)容請(qǐng)閱讀官方文檔特笋。
(7)觸發(fā)內(nèi)存泄漏:內(nèi)存泄露越小,就需要運(yùn)行更長的時(shí)間發(fā)現(xiàn)泄露點(diǎn)巾兆,可以通過橫豎屏切換猎物,應(yīng)用切換來觸發(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)存部分的簡單介紹,更具體的內(nèi)容請(qǐng)閱讀官方文檔剪侮。
移動(dòng)App性能評(píng)測(cè)與優(yōu)化內(nèi)存篇相關(guān)內(nèi)容
主要將自己認(rèn)為重要的內(nèi)容記錄下來拭宁,部分內(nèi)容會(huì)根據(jù)最新的Android版本加入一些自己的理解。雖然Android一直在不停的演進(jìn)瓣俯,書中描述的部分問題在最新Android手機(jī)上已經(jīng)不存在杰标,但是書中描述的一些分析方法和經(jīng)驗(yàn)還是很值得學(xué)習(xí)的。
MAT工具使用技巧
MAT打開hprof文件之后彩匕,使用Top Consumers和Component Report功能腔剂,使用這些功能能快速定位大塊內(nèi)存消耗,由于虛擬機(jī)不會(huì)區(qū)分系統(tǒng)資料和應(yīng)用自身的對(duì)象驼仪,可以采用兩種方式來區(qū)分系統(tǒng)框架資源和應(yīng)用自身對(duì)象
方法一:hprof-conv轉(zhuǎn)換時(shí)添加“-z”參數(shù)掸犬;
方法二:hprof已經(jīng)轉(zhuǎn)換過了,在數(shù)據(jù)中尋找應(yīng)用的Application類對(duì)象绪爸,可以使用OQL語句查詢應(yīng)用自身對(duì)象湾碎;
使用-z和OQL查詢語句得到的對(duì)象集合就是應(yīng)用代碼分配的部分。這樣就剔除了系統(tǒng)資源的影響奠货。
Dilvik Heap常見問題及相關(guān)分析工具
(1)功能反復(fù)執(zhí)行介褥,Heap一直在持續(xù)增長,這種情況通常出現(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等工具來查看具體分配的對(duì)象;
(3)每次啟動(dòng)應(yīng)用之后喉前,Heap內(nèi)存相對(duì)以前版本穩(wěn)定增長没酣,可能是由于新功能機(jī)代碼改動(dòng)引入的固定內(nèi)存增長,獲取Heap Dump進(jìn)行多版本使用前后對(duì)比來查找增長原因卵迂。
(4)Heap Alloc變化不大裕便,但進(jìn)程Dalvik Heap Pss內(nèi)存明顯增加,是由于分配了大量小對(duì)象造成內(nèi)存碎片的原因见咒。
新問題
新功能可能會(huì)分配幾萬到幾十萬字節(jié)的內(nèi)存偿衰,實(shí)際增加的內(nèi)存為2MB,但是Dalvik heap內(nèi)存并沒有增加太多(200kb)改览,說明問題不在Dalvik里就能解決下翎,需要我們進(jìn)一步深挖,Heap內(nèi)存并不是應(yīng)用的全部宝当,可以通過dumpsys查看應(yīng)用整個(gè)進(jìn)程的使用量视事,以及各部分的使用量,最后發(fā)現(xiàn)Dalvik heap Pss部分增加比較多庆揩。最常用觀察進(jìn)程內(nèi)存的方法 adb shell dumpsys meminfo packge name| pid
上述描述的問題就是Dalvik heap pss內(nèi)存增加了2M俐东,Dalvik heap Alloc值增長了273kb,但Dalvik heap Free也能看出大部分增長的內(nèi)存是處于空閑狀態(tài)的订晌。各種怪異的問題虏辫,常用的方法找不出原因,說明有更深層次的原因锈拨,Java代碼的內(nèi)存分配和釋放是由虛擬機(jī)管理的乒裆,我們需要通過虛擬機(jī)機(jī)制來探索內(nèi)存增長的原因。
Dalvik heap內(nèi)部機(jī)制
(1)為什么DVM占用內(nèi)存不釋放推励,需要于都DVM內(nèi)存分配代碼(位于dalvik/vm/alloc下)鹤耍,目前ART虛擬機(jī)已經(jīng)取代了DVM,具體代碼位置沒有看過验辞。
(2)新建對(duì)象之后稿黄,由于要向?qū)?yīng)的地址寫入數(shù)據(jù),內(nèi)核開始真正分配該地址對(duì)應(yīng)的4KB物理內(nèi)存頁面跌造。代碼在Alloc.cpp中杆怕。
(3)運(yùn)行一段時(shí)間之后開始GC族购,GC時(shí)可能會(huì)進(jìn)行trim,即將空閑的物理頁面釋放回系統(tǒng)陵珍,表現(xiàn)為private dirty/pss下降寝杖,相關(guān)代碼在HeapSource.cpp中。
問題所在以及優(yōu)化
(1)在了解DVM分配和釋放內(nèi)存的機(jī)制之后互纯,根據(jù)dumpsys觀察到的現(xiàn)象瑟幕,猜測(cè)可能是頁面利用率的問題,如在GC之后留潦,大部分對(duì)象被釋放只盹,少部分留下來,導(dǎo)致整頁的4KB內(nèi)存可能只有一個(gè)小對(duì)象兔院,但統(tǒng)計(jì)的時(shí)候是按4KB來計(jì)算殖卑。
(2)將MAT中的數(shù)據(jù)導(dǎo)出為csv格式,然后按也頁面進(jìn)行統(tǒng)計(jì)坊萝,可以查看也頁面利用率統(tǒng)計(jì)結(jié)果圖孵稽,利用率低的頁面增加說明小對(duì)象碎片數(shù)量增加。
(3)取出步驟2中使用不滿2KB的頁面的內(nèi)存塊地址十偶,重新導(dǎo)入MAT得到對(duì)象列表菩鲜,基本可以看出那些對(duì)象造成了內(nèi)存的碎片化。
(4)問題基本過程還原:生成對(duì)象過程需要很多臨時(shí)變量扯键,批量生成過程中還有空閑內(nèi)存睦袖,虛擬機(jī)沒有垃圾回收珊肃,完成后進(jìn)行垃圾回收荣刑,清楚了所有的臨時(shí)變量,留下碎片化內(nè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í)行以上代碼之后通過MAT查看數(shù)組每個(gè)成員的內(nèi)存地址厉亏,發(fā)現(xiàn)都是不連續(xù)的,這就到消耗很多的物理頁面烈和,增加Heap Free爱只。造成例子中的問題(書中沒有描述具體如何操作,我自己也沒有嘗試招刹,感興趣的可以試驗(yàn)一下恬试,但是該問題在Android使用ART虛擬機(jī)之后就不會(huì)存在碎片化問題)。
總結(jié)
(1)MAT是探索java堆并發(fā)現(xiàn)問題的好工具疯暑,能快速發(fā)現(xiàn)常見圖片和大數(shù)組問題训柴,但是MAT不是萬能的,比如該問題隱藏在對(duì)象地址中妇拯。內(nèi)存分配的最小單位是頁面幻馁,大小通常為4KB洗鸵;盡量不要在循環(huán)中創(chuàng)建很多臨時(shí)變量,可以將大型循環(huán)拆開仗嗦、分段膘滨、按需執(zhí)行;
(2)在JVM中稀拐,虛擬機(jī)借助標(biāo)記整理算法將散布的內(nèi)存移動(dòng)到一起火邓,這樣就不存在頁面利用率的問題,但是在DVM由于使用Mark-Sweep標(biāo)記清除算法钩蚊,該算法不能移動(dòng)對(duì)象贡翘,即沒有內(nèi)存整理,這樣就導(dǎo)致了內(nèi)存碎片問題砰逻,導(dǎo)致以上問題的產(chǎn)生鸣驱。目前Android使用ART虛擬機(jī)取代DVM,ART使用了標(biāo)記整理算法進(jìn)行內(nèi)存回收蝠咆,所以使用ART虛擬機(jī)的Android系統(tǒng)就不存在內(nèi)存碎片問題踊东,DVM與ART虛擬機(jī)區(qū)別可參考JVM、DVM以及ART虛擬機(jī)簡介
內(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出來,由于zygote已經(jīng)載入完成的Dalvik虛擬機(jī)和Android應(yīng)用框架的代碼溉知,fork出來的進(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庫(共享內(nèi)存)
(5)應(yīng)用的代碼(私有內(nèi)存)
(6)應(yīng)用的資源(私有內(nèi)存)
(7)應(yīng)用的so庫(私有內(nèi)存)
(8)堆內(nèi)存舌劳、其它部分(共享/私有)帚湘。
通過dumpsys meminfo可以觀察內(nèi)存值,它將不同額內(nèi)存消耗分類統(tǒng)計(jì)甚淡,通過閱讀和分析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/中獲取撵割。我們可以通過 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-開頭的內(nèi)存區(qū)域歸為Dalvik-other
(3)文件的mmap按已知的幾個(gè)擴(kuò)展名分類
(4)其余歸為Other mmap啡彬;
由于Android已使用ART虛擬機(jī)代替Dalvik虛擬機(jī)羹与,暫時(shí)不知道最新的smaps信息如何對(duì)內(nèi)存進(jìn)行分類。
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ì)被其它使用峻呕,該共享庫的進(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存放的是類的數(shù)據(jù)結(jié)構(gòu)及關(guān)系,Dex mmap是類函數(shù)代碼和常量干花,通常優(yōu)化這部分內(nèi)存妄帘,需要從代碼出發(fā),但如果我們深入理解系統(tǒng)池凄,也能夠找到其它方法來降低這部分的內(nèi)存消耗抡驼,所以我們?cè)趦?yōu)化內(nèi)存時(shí),不應(yīng)該只優(yōu)化堆內(nèi)存肿仑,在我們搞定其它類型內(nèi)存的含義以及原理之后致盟,也是能夠?qū)ζ渌糠值膬?nèi)存進(jìn)行優(yōu)化碎税。雖然最新的Android版本Dalvik Other占用的內(nèi)存雖然不大,但是這給優(yōu)化內(nèi)存提供了一種思路馏锡。簡單一段代碼在一個(gè)空應(yīng)用執(zhí)行以后雷蹂,可以看到對(duì)應(yīng)heap、other杯道、dex mmap的內(nèi)存增長匪煌,heap增長可以通過代碼邏輯分析出來這段代碼需要分配多少,也可以在mat中看到新建對(duì)象消耗的內(nèi)存党巾,當(dāng)應(yīng)用使用完新創(chuàng)建的對(duì)象后萎庭,就會(huì)將heap內(nèi)存釋放,但是other和dex mmap不會(huì)被釋放齿拂。
一個(gè)類的內(nèi)存消耗以及new一個(gè)對(duì)象的步驟
虛擬機(jī)在執(zhí)行這步時(shí)會(huì)做什么那驳规?
第一步是loadClass操作,將類信息從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ì)象引用了其它類型贯底,那還需要先按照同樣的邏輯創(chuàng)建被引用的class,在創(chuàng)建一個(gè)類實(shí)例的每一步都需要消耗內(nèi)存撒强,可以大概計(jì)算一下new操作需要消耗的內(nèi)存禽捆;根據(jù)虛擬機(jī)的代碼能夠得知class根據(jù)類成員和函數(shù)數(shù)目分配linearAlloc和aux-structure的多少,以及class本身及函數(shù)需要的字節(jié)數(shù)飘哨,我們?cè)俑鶕?jù)所以class總量進(jìn)行平均計(jì)算得到一組數(shù)據(jù):
第一步是loadClass操作胚想,加載類信息;
(1).dex mmap:載入一個(gè)類需要先讀取259字節(jié)的mmap
(2)dalvik-linearAlloc:在linearAlloc區(qū)域分配437字節(jié)芽隆,存放類的靜態(tài)數(shù)據(jù)浊服;
(3)dalvik-aux-structure:在aux區(qū)域分配88字節(jié),存放各種指針
第二步:new instance操作胚吁,創(chuàng)建對(duì)象實(shí)例:
(1)dex mmap :為了執(zhí)行類的構(gòu)造函數(shù)牙躺,還需要讀取252字節(jié)mmap
(2)dalvik-heap:根據(jù)類的具體內(nèi)容而變化。
由于內(nèi)存最小分配單位是頁面腕扶,同時(shí)內(nèi)存分配并不是連續(xù)分布孽拷,所以可能需要分配多個(gè)4KB頁面。
Dex mmap在Android應(yīng)用中作用是映射class.dex文件半抱,Dilvik虛擬機(jī)需要從dex文件中加載類信息脓恕、字符串常量膜宋,需要在調(diào)用函數(shù)時(shí)直接從mmap內(nèi)存中讀取函數(shù)代碼來執(zhí)行,所以該部分內(nèi)存是程序運(yùn)行必不可少的炼幔。
.dex文件將所有的class里邊所包含的信息全部整合在一個(gè)激蹲。可以使用Android SDK提供的dexdump工具來觀察dex文件內(nèi)容江掩,假設(shè)代碼里用到A1類后学辱,還用到B1、C1环形、D1類策泣,如果能在dex文件中將A1、B1抬吟、C1萨咕、D1類放在一起,虛擬機(jī)就只需要加載一個(gè)4KB的頁面火本,可以減少內(nèi)存使用危队。優(yōu)化思路就是調(diào)整dex文件中數(shù)據(jù)的順序,盡量將使用到的數(shù)據(jù)內(nèi)容排列在一起钙畔。Proguard工具能夠?qū)︻惷M(jìn)行修改茫陆,根據(jù)程序運(yùn)行的邏輯將那些會(huì)互相調(diào)用的類改為同一個(gè)packag名,這樣就可以使他們的數(shù)據(jù)排列在一起擎析。
小結(jié)
(1)優(yōu)化內(nèi)存時(shí)簿盅,不只有堆內(nè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:類的數(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)問題好工具舟陆;
經(jīng)驗(yàn)總結(jié):
(1)內(nèi)存分配的最小單位是頁面,通常為4KB耻矮;
(2)碎片不僅僅是Dalvik內(nèi)存秦躯,還包括各種mmap可能產(chǎn)生的內(nèi)存碎片,在Android4.4引入ART虛擬機(jī)之后裆装,ART虛擬機(jī)的垃圾收集器(MarkSweep+Semispace)會(huì)進(jì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)化琢唾,其它類型的內(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)存問題實(shí)例分析