原文:https://source.android.com/devices/tech/perf/low-ram
介紹
Android現(xiàn)在支持512MB RAM的設(shè)備。本文旨在幫助OEM為低內(nèi)存設(shè)備優(yōu)化和配置Android 4.4靶草。其中一些優(yōu)化非常通用挖帘,它們也可以應(yīng)用于以前的版本宫静。
Android 4.4平臺(tái)優(yōu)化
改進(jìn)了內(nèi)存管理
- 驗(yàn)證的內(nèi)存節(jié)省內(nèi)核配置:交換到ZRAM洋访。
- 如果要緩存且太大器钟,則終止緩存進(jìn)程摊鸡。
- 不允許大型服務(wù)將自己放回A服務(wù)中(因此它們不會(huì)導(dǎo)致啟動(dòng)器被終止)绽媒。
- 終止在空閑維護(hù)中過(guò)大的進(jìn)程(即使是通常不可用的,例如當(dāng)前的IME)免猾。
- 序列化后臺(tái)服務(wù)的啟動(dòng)是辕。
- 調(diào)整低RAM設(shè)備的內(nèi)存使用:更嚴(yán)格的內(nèi)存不足(OOM)調(diào)整級(jí)別,更小的圖形緩存等猎提。
減少系統(tǒng)內(nèi)存
- 裁剪system_server和SystemUI進(jìn)程(節(jié)省幾MB)获三。
- 在Dalvik中預(yù)加載dex緩存(節(jié)省幾MB)。
- 驗(yàn)證JIT-off選項(xiàng)(每個(gè)進(jìn)程最多可節(jié)省1.5MB)。
- 減少每進(jìn)程字體緩存開(kāi)銷(xiāo)石窑。
- 引入ArrayMap / ArraySet并在Framework中廣泛使用牌芋,作為HashMap / HashSet的輕量級(jí)替代品。
進(jìn)程統(tǒng)計(jì)
添加新的開(kāi)發(fā)者選項(xiàng)松逊,用于顯示內(nèi)存狀態(tài)和應(yīng)用程序內(nèi)存使用情況躺屁,并根據(jù)運(yùn)行頻率和內(nèi)存消耗量進(jìn)行排序。
API
添加新的ActivityManager.isLowRamDevice()经宏,用于允許應(yīng)用程序檢測(cè)何時(shí)在低內(nèi)存設(shè)備上運(yùn)行并選擇禁用大RAM功能犀暑。
內(nèi)存跟蹤
新memtrack HAL用于跟蹤圖形內(nèi)存分配,dumpsys meminfo
中的附加信息烁兰,meminfo中的概要說(shuō)明(例如報(bào)告的可用RAM包括緩存進(jìn)程的RAM耐亏,以便OEM不會(huì)嘗試優(yōu)化錯(cuò)誤的東西)。
構(gòu)建時(shí)配置
啟用低Ram設(shè)備標(biāo)志
我們正在引入一個(gè)名為ActivityManager.isLowRamDevice()
的新API 沪斟,以便應(yīng)用程序確定是否應(yīng)該關(guān)閉在低內(nèi)存設(shè)備上運(yùn)行不佳的特定內(nèi)存密集型功能广辰。
對(duì)于512MB設(shè)備,此API有望返回true
主之。它可以通過(guò)設(shè)備makefile中的以下系統(tǒng)屬性啟用择吊。
PRODUCT_PROPERTY_OVERRIDES + = ro.config.low_ram = true
Launcher配置
確保Launcher上的默認(rèn)壁紙?jiān)O(shè)置不 使用動(dòng)態(tài)壁紙。低內(nèi)存設(shè)備不應(yīng)預(yù)安裝任何動(dòng)態(tài)壁紙槽奕。
內(nèi)核配置
調(diào)整內(nèi)核/ActivityManager以減少直接回收
當(dāng)進(jìn)程或內(nèi)核嘗試分配一頁(yè)內(nèi)存(直接或由于新頁(yè)面中的錯(cuò)誤)并且內(nèi)核已使用所有可用空閑內(nèi)存時(shí)几睛,會(huì)發(fā)生直接回收。這要求內(nèi)核在釋放頁(yè)面時(shí)阻止分配粤攒。相反所森,這經(jīng)常需要磁盤(pán)I/O清除垃圾文件(備份頁(yè)面)或等待lowmemorykiller
終止進(jìn)程。這可能導(dǎo)致任何線程中的額外I/O夯接,包括UI線程焕济。
為避免直接回收,內(nèi)核具有觸發(fā)kswapd
或后臺(tái)回收的水印盔几。這是一個(gè)試圖釋放頁(yè)面的線程吼蚁,因此下一次真正的線程分配它可以快速成功。
觸發(fā)后臺(tái)回收的默認(rèn)閾值相當(dāng)?shù)停?GB設(shè)備上約為2MB问欠,512MB設(shè)備上為636KB。并且內(nèi)核回收在后臺(tái)只回收了幾MB的內(nèi)存粒蜈。這意味著快速分配超過(guò)幾兆字節(jié)的任何進(jìn)程都將很快導(dǎo)致直接回收顺献。
android-3.4內(nèi)核分支中添加了對(duì)新內(nèi)核可調(diào)參數(shù)的支持,見(jiàn)補(bǔ)丁92189d47f66c67e5fd92eafaa287e153197a454f(“添加額外的空閑kbytes可調(diào)”)枯怖。將此補(bǔ)丁合入到設(shè)備的內(nèi)核將允許ActivityManager告訴內(nèi)核嘗試保留3個(gè)全屏32 bpp內(nèi)存緩沖區(qū)注整。
可以通過(guò)框架config.xml配置這些閾值
<!-- Device configuration setting the /proc/sys/vm/extra_free_kbytes tunable
in the kernel (if it exists). A high value will increase the amount of memory
that the kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier. A low value allows more memory to be used by
processes but may cause more allocations to block waiting on disk I/O or
lowmemorykiller. Overrides the default value chosen by ActivityManager based
on screen size. 0 prevents keeping any extra memory over what the kernel keeps
by default. -1 keeps the default. -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the /proc/sys/vm/extra_free_kbytes
tunable in the kernel (if it exists). 0 uses the default value chosen by
ActivityManager. A positive value will increase the amount of memory that the
kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier. A negative value allows more memory to be
used by processes but may cause more allocations to block waiting on disk I/O
or lowmemorykiller. Directly added to the default value chosen by
ActivityManager based on screen size. -->
<integer name="config_extraFreeKbytesAdjust">0</integer>
調(diào)整LowMemoryKiller
ActivityManager配置LowMemoryKiller的閾值,以匹配在每個(gè)優(yōu)先級(jí)存儲(chǔ)桶中運(yùn)行進(jìn)程所需的文件(支持頁(yè)面(緩存頁(yè)面))的工作集的期望。如果設(shè)備對(duì)工作集具有高要求肿轨,例如寿冕,如果廠商UI需要更多內(nèi)存或者如果添加了更多服務(wù),則可以增加閾值椒袍。
如果為文件支持的頁(yè)面保留了太多內(nèi)存驼唱,則可以減少閾值,因此在因緩存變得太小而導(dǎo)致磁盤(pán)抖動(dòng)發(fā)生之前后臺(tái)進(jìn)程將會(huì)被長(zhǎng)時(shí)間終止驹暑。
<!-- Device configuration setting the minfree tunable in the lowmemorykiller
in the kernel. A high value will cause the lowmemorykiller to fire earlier,
keeping more memory in the file cache and preventing I/O thrashing, but
allowing fewer processes to stay in memory. A low value will keep more
processes in memory but may cause thrashing if set too low. Overrides the
default value chosen by ActivityManager based on screen size and total memory
for the largest lowmemorykiller bucket, and scaled proportionally to the
smaller buckets. -1 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the minfree tunable in the
lowmemorykiller in the kernel. A high value will cause the lowmemorykiller to
fire earlier, keeping more memory in the file cache and preventing I/O
thrashing, but allowing fewer processes to stay in memory. A low value will
keep more processes in memory but may cause thrashing if set too low. Directly
added to the default value chosen by ActivityManager based on screen
size and total memory for the largest lowmemorykiller bucket, and scaled
proportionally to the smaller buckets. 0 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>
交換到zRAM
zRAM交換可以通過(guò)壓縮內(nèi)存頁(yè)面并將它們放入動(dòng)態(tài)分配的內(nèi)存交換區(qū)域來(lái)增加系統(tǒng)中可用的內(nèi)存量玫恳。
同樣,由于內(nèi)存的小幅增加會(huì)占用CPU時(shí)間优俘,因此您應(yīng)該仔細(xì)測(cè)量zRAM交換對(duì)系統(tǒng)的性能影響京办。
Android在幾個(gè)級(jí)別處理交換到zRAM:
- 首先,必須啟用以下內(nèi)核選項(xiàng)才能有效地使用zRAM交換:
CONFIG_SWAP
CONFIG_CGROUP_MEM_RES_CTLR
CONFIG_CGROUP_MEM_RES_CTLR_SWAP
CONFIG_ZRAM
- 然后帆焕,您應(yīng)該在fstab中添加一行如下所示:
/dev/block/zram0 none swap defaults zramsize=<size in bytes>,swapprio=<swap partition priority>
* `zramsize`是必需的惭婿,表示您希望zram區(qū)域保留多少未壓縮的內(nèi)存。通常觀察到30-50%范圍內(nèi)的壓縮比叶雹。
* `swapprio` 是可選的财饥,如果沒(méi)有多個(gè)交換區(qū)域則不需要。
您還應(yīng)確保在設(shè)備特定的[ sepolicy/file_contexts](https://source.android.com/security/selinux/implement.html)中將關(guān)聯(lián)的塊設(shè)備標(biāo)記為swap_block_device浑娜,以便SELinux正確處理它佑力。
/dev/block/zram0 u:object_r:swap_block_device:s0
- 默認(rèn)情況下,Linux內(nèi)核一次交換8頁(yè)內(nèi)存筋遭。使用ZRAM時(shí)打颤,一次讀取1頁(yè)的增量成本可以忽略不計(jì),并且可能有助于設(shè)備處于極端內(nèi)存壓力下漓滔。要一次只讀1頁(yè)编饺,請(qǐng)將以下內(nèi)容添加到
init.rc
:
write /proc/sys/vm/page-cluster 0
- 在你
init.rc
的mount_all /fstab.X
行后,添加:
swapon_all /fstab.X
- 如果在內(nèi)核中啟用了該功能响驴,則會(huì)在引導(dǎo)時(shí)自動(dòng)配置內(nèi)存cgroup透且。
- 如果內(nèi)存cgroup可用,則ActivityManager會(huì)將優(yōu)先級(jí)較低的線程標(biāo)記為比其他線程更可交換豁鲤。如果需要內(nèi)存秽誊,Android內(nèi)核將開(kāi)始將內(nèi)存頁(yè)面遷移到zRAM交換區(qū),為已經(jīng)由ActivityManager標(biāo)記的內(nèi)存頁(yè)面提供更高的優(yōu)先級(jí)琳骡。
Carveouts锅论,Ion和連續(xù)內(nèi)存分配(CMA)
特別重要的是在低內(nèi)存設(shè)備上要注意清理,特別是那些并不總是被充分利用的設(shè)備 - 例如用于安全視頻播放的分區(qū)楣号。有幾種解決方案可以根據(jù)硬件的具體要求最易,最大限度地減少削減區(qū)域的影響怒坯。
如果硬件允許不連續(xù)的內(nèi)存分配,離子系統(tǒng)堆允許從系統(tǒng)內(nèi)存分配內(nèi)存藻懒,從而無(wú)需進(jìn)行分割剔猿。它還試圖進(jìn)行大量分配以消除外圍設(shè)備上的TLB壓力。如果存儲(chǔ)區(qū)必須是連續(xù)的或局限于特定的地址范圍嬉荆,則可以使用連續(xù)的存儲(chǔ)器分配器(CMA)归敬。
這創(chuàng)建了一個(gè)系統(tǒng)也可以用于可移動(dòng)頁(yè)面的分割。當(dāng)需要該區(qū)域時(shí)员寇,可移動(dòng)頁(yè)面將從中移出弄慰,允許系統(tǒng)在空閑時(shí)將大型分區(qū)用于其他目的。使用離子cma堆蝶锋,CMA可以直接使用或更簡(jiǎn)單地通過(guò)離子使用陆爽。
應(yīng)用優(yōu)化提示
- 查看管理您的應(yīng)用內(nèi)存以及以前相同主題的博文:
- 檢查/刪除預(yù)裝應(yīng)用程序中未使用的assets - development/tools/findunused(應(yīng)該有助于縮小應(yīng)用程序)。
- assets中使用PNG格式扳缕,尤其是當(dāng)它們具有透明區(qū)域時(shí)
- 如果編寫(xiě)本機(jī)代碼慌闭,請(qǐng)使用calloc()而不是malloc/memset
- 不要啟用將Parcel數(shù)據(jù)寫(xiě)入磁盤(pán)并稍后讀取的代碼。
- 不要訂閱安裝的每個(gè)軟件包躯舔,而是使用ssp過(guò)濾驴剔。添加如下所示的過(guò)濾:
<data android:scheme="package" android:ssp="com.android.pkg1" />
<data android:scheme="package" android:ssp="com.myapp.act1" />
了解Android中的各種流程狀態(tài)
SERVICE - SERVICE_RESTARTING
應(yīng)用程序由于自己的原因使自己在后臺(tái)運(yùn)行。最常見(jiàn)的問(wèn)題應(yīng)用程序在后臺(tái)運(yùn)行時(shí)太多了粥庄。%duration * pss可能是一個(gè)很好的“壞”度量標(biāo)準(zhǔn)丧失,雖然這個(gè)集合非常集中,只是做持續(xù)時(shí)間百分比可能更好地關(guān)注我們根本不想讓它們運(yùn)行的??事實(shí)惜互。IMPORTANT_FOREGROUND - RECEIVER
應(yīng)用程序由于任何原因在后臺(tái)運(yùn)行(不直接與用戶交互)布讹。這些都為系統(tǒng)增加了內(nèi)存負(fù)載。在這種情況下训堆,(%duration * pss)badness值可能是這些進(jìn)程的最佳排序描验,因?yàn)槠渲性S多將始終運(yùn)行的原因很充分,因此它們的pss大小作為其內(nèi)存負(fù)載的一部分非常重要坑鱼。PERSISTENT
持久性系統(tǒng)流程膘流。跟蹤pss以觀察這些過(guò)程變得過(guò)大。TOP
用戶當(dāng)前正在與之交互的流程鲁沥。同樣呼股,pss是這里的重要指標(biāo),顯示應(yīng)用程序在使用時(shí)創(chuàng)建的內(nèi)存負(fù)載量画恰。HOME - CACHED_EMPTY
所有這些在底部的過(guò)程都是系統(tǒng)保留的過(guò)程卖怜,以防再次需要它們; 但是他們可以隨時(shí)自由終止并在需要時(shí)重新創(chuàng)建。這些是我們計(jì)算內(nèi)存狀態(tài)的基礎(chǔ) - 正常阐枣,中等马靠,低,關(guān)鍵是基于系統(tǒng)可以保留多少這些進(jìn)程蔼两。對(duì)于這些過(guò)程來(lái)說(shuō)甩鳄,關(guān)鍵是pss; 這些進(jìn)程應(yīng)該在它們處于這種狀態(tài)時(shí)盡可能地減少它們的內(nèi)存占用,以允許保持最大的進(jìn)程總數(shù)额划。一般來(lái)說(shuō)妙啃,表現(xiàn)良好的應(yīng)用程序的pss占用空間在此狀態(tài)下比在TOP時(shí)要小得多。TOP與CACHED_ACTIVITY-CACHED_ACTIVITY_CLIENT比較
進(jìn)程為T(mén)OP時(shí)的pss與處于這些特定緩存狀態(tài)之間的pss之間的差異是查看進(jìn)入后臺(tái)時(shí)釋放內(nèi)存的最佳數(shù)據(jù)俊戳。排除CACHED_EMPTY狀態(tài)會(huì)使這些數(shù)據(jù)更好揖赴,因?yàn)槌藞?zhí)行UI之外,由于某些原因它會(huì)刪除進(jìn)程啟動(dòng)時(shí)的情況抑胎,因此不必處理與用戶交互時(shí)獲得的所有UI開(kāi)銷(xiāo)燥滑。
分析
分析應(yīng)用啟動(dòng)時(shí)間
在應(yīng)用啟動(dòng)時(shí),使用$ adb shell am start
命令加上 -P
或 --start-profiler
選項(xiàng)運(yùn)行探查器阿逃。在您的任何代碼加載到zygote之前铭拧,這將在您的進(jìn)程從zygote創(chuàng)建后立即啟動(dòng)探查器。
使用bugreports進(jìn)行分析
包含當(dāng)前可用于調(diào)試的各種信息恃锉。這些服務(wù)包括batterystats
搀菩,netstats
, procstats
破托,和usagestats
肪跋。您可以使用以下行找到它們:
------ CHECKIN BATTERYSTATS (dumpsys batterystats --checkin) ------
7,0,h,-2558644,97,1946288161,3,2,0,340,4183
7,0,h,-2553041,97,1946288161,3,2,0,340,4183
檢查任何持久進(jìn)程
重新啟動(dòng)設(shè)備并檢查進(jìn)程。
運(yùn)行幾個(gè)小時(shí)土砂,然后再次檢查進(jìn)程州既。不應(yīng)該有任何長(zhǎng)時(shí)間運(yùn)行的進(jìn)程。
進(jìn)行長(zhǎng)壽測(cè)試
運(yùn)行更長(zhǎng)的持續(xù)時(shí)間并跟蹤進(jìn)程的內(nèi)存瘟芝。它增加了嗎易桃?它保持不變嗎?創(chuàng)建Canonical用例并在這些場(chǎng)景下運(yùn)行長(zhǎng)壽測(cè)試锌俱。