Android 中的卡顿丢帧原因概述 - 低内存篇
在Android 中的卡頓丟幀原因概述 - 系統(tǒng)篇 這篇文章中 , 實(shí)際案例這里我們有列舉一些由于系統(tǒng)低內(nèi)存導(dǎo)致的卡頓 , 由于 Android 低內(nèi)存對整機(jī)性能影響比較大 , 所以單獨(dú)寫一篇文章 , 來概述系統(tǒng)低內(nèi)存對整機(jī)性能的影響 .
隨著 Android 系統(tǒng)版本的更迭 , 以及 App 的代碼膨脹 , Android 系統(tǒng)對內(nèi)存的需求越來越大 , 但是目前市面上還存在著大量的 4G 內(nèi)存以下的機(jī)器 , 這部分用戶就很容易遇到整機(jī)低內(nèi)存的情況 , 尤其是在系統(tǒng)大版本更新和 App 越裝越多的情況下 .
Android 低內(nèi)存會導(dǎo)致性能問題 , 具體表現(xiàn)就是響應(yīng)慢和卡頓 . 比如啟動一個(gè)應(yīng)用要花比平時(shí)更長的時(shí)間 ; 滑動列表會掉更多幀 ; 后臺的進(jìn)程減少導(dǎo)致冷啟動變多 ; 手機(jī)很容易發(fā)熱發(fā)燙等 , 下面我會概述發(fā)生這些性能問題的原因 . Debug 的方法 , 以及可能的優(yōu)化措施 .
Android 中的卡頓丟幀原因概述 - 方法論[1]
Android 中的卡頓丟幀原因概述 - 系統(tǒng)篇[2]
Android 中的卡頓丟幀原因概述 - 應(yīng)用篇[3]
Android 中的卡頓丟幀原因概述 - 低內(nèi)存篇[4]
低內(nèi)存的數(shù)據(jù)特征和行為特征
Meminfo 信息
最簡單的方法是使用 Android 系統(tǒng)自帶的 Dumpsys meminfo 工具
adb shell dumpsys meminfo ...... Total RAM: 7,658,060K (status moderate)Free RAM: 550,200K ( 78,760K cached pss + 156,ba480K cached kernel + 314,960K free)Used RAM: 7,718,091K (6,118,703K used pss + 1,599,388K kernel)Lost RAM: -319,863KZRAM: 2,608K physical used for 301,256K in swap (4,247,544K total swap)Tuning: 256 (large 512), oom 322,560K, restore limit 107,520K (high-end-gfx)如果系統(tǒng)處于低內(nèi)存的話 , 會有如下特征:
FreeRam 的值非常少 , Used RAM 的值非常大
ZRAM 使用率非常高(如果開了 Zram 的話)
LMK && kswapd 線程活躍
低內(nèi)存的時(shí)候, LKMD 會非常活躍, 在 Kernel Log 里面可以看到 LMK 殺進(jìn)程的信息:
[kswapd0] lowmemorykiller: Killing 'u.mzsyncservice' (15609) (tgid 15609), adj 906, to free 28864kB on behalf of 'kswapd0' (91) because cache 258652kB is below limit 261272kB for oom score 906 Free memory is -5540kB above reserved. Free CMA is 3172kB Total reserve is 227288kB Total free pages is 271748kB Total file cache is 345384kB GFP mask is 0x14000c0上面這段 Log 的意思是說, 由于 mem 低于我們設(shè)定的 900 的水位線 (261272kB),所以把 pid 為 15609 的 mzsyncservice 這個(gè)進(jìn)程殺掉(這個(gè)進(jìn)程的 adj 是 906 )
proc/meminfo
這里是 Linux Kernel 展示 meminfo 的地方 , 關(guān)于 meminfo 的解讀,可以參考這篇文章:/PROC/MEMINFO 之謎[5]
從結(jié)果來 , 當(dāng)系統(tǒng)處于低內(nèi)存的情況時(shí)候 , MemFree 和 MemAvailable 的值都很小
MemTotal: 5630104 kB MemFree: 148928 kB MemAvailable: 864172 kB Buffers: 28464 kB Cached: 1003144 kB SwapCached: 19844 kB Active: 1607512 kB Inactive: 969208 kB Active(anon): 1187828 kB Inactive(anon): 426192 kB Active(file): 419684 kB Inactive(file): 543016 kB Unevictable: 62152 kB Mlocked: 62152 kB SwapTotal: 2097148 kB SwapFree: 42576 kB Dirty: 3604 kB Writeback: 0 kB AnonPages: 1602928 kB Mapped: 996768 kB Shmem: 7284 kB Slab: 306440 kB SReclaimable: 72320 kB SUnreclaim: 234120 kB KernelStack: 89776 kB PageTables: 107572 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 4912200 kB Committed_AS: 118487976 kB VmallocTotal: 263061440 kB VmallocUsed: 0 kB VmallocChunk: 0 kB CmaTotal: 303104 kB CmaFree: 3924 kB整機(jī)卡頓 && 響應(yīng)慢
低內(nèi)存的時(shí)候,整機(jī)使用的時(shí)候要比非低內(nèi)存的時(shí)候要卡很多,點(diǎn)擊應(yīng)用或者啟動 App 都會有不順暢或者響應(yīng)慢的感覺
低內(nèi)存對性能的具體影響
影響主線程 IO 操作
主線程出現(xiàn)大量的 IO 相關(guān)的問題 ,
反饋到 Trace 上就是有大量的黃色 Trace State 出現(xiàn) , 例如 : Uninterruptible Sleep | WakeKill - Block I/O .
查看其 Block 信息 (kernel callsite when blocked:: "wait_on_page_bit_killable+0x78/0x88)
Linux 系統(tǒng)的 page cache 鏈表中有時(shí)會出現(xiàn)一些還沒準(zhǔn)備好的 page ( 即還沒把磁盤中的內(nèi)容完全地讀出來 ) , 而正好此時(shí)用戶在訪問這個(gè) page 時(shí)就會出現(xiàn) wait_on_page_locked_killable 阻塞了. 只有系統(tǒng)當(dāng) io 操作很繁忙時(shí), 每筆的 io 操作都需要等待排隊(duì)時(shí), 極其容易出現(xiàn)且阻塞的時(shí)間往往會比較長.
當(dāng)出現(xiàn)大量的 IO 操作的時(shí)候,應(yīng)用主線程的 Uninterruptible Sleep 也會變多,此時(shí)涉及到 io 操作(比如 view ,讀文件,讀配置文件、讀 odex 文件),都會觸發(fā) Uninterruptible Sleep , 導(dǎo)致整個(gè)操作的時(shí)間變長
Uninterruptible Sleep - Block IO Uninterruptible Sleep - Block IO出現(xiàn) CPU 競爭
低內(nèi)存會觸發(fā) Low Memory Killer 進(jìn)程頻繁進(jìn)行掃描和殺進(jìn)程,kswapd0 是一個(gè)內(nèi)核工作線程,內(nèi)存不足時(shí)會被喚醒,做內(nèi)存回收的工作。當(dāng)內(nèi)存頻繁在低水位的時(shí)候,kswapd0 會被頻繁喚醒,占用 cpu ,造成卡頓和耗電。
比如下面這個(gè)情況, kswapd0 占用了 855 的超大核 cpu7 ,而且是滿頻在跑,耗電可想而知,如果此時(shí)前臺應(yīng)用的主線程跑到了 cpu7 上,很大可能會出現(xiàn) cpu 競爭,導(dǎo)致調(diào)度不到而丟幀。
kswapd0 占滿大核HeapTaskDaemon 通常也會在低內(nèi)存的時(shí)候跑的很高,來做內(nèi)存回收相關(guān)的操作
進(jìn)程頻繁查殺和重啟
對 AMS 的影響主要集中在進(jìn)程的查殺上面 , 由于 LMK 的介入 , 處于 Cache 狀態(tài)的進(jìn)程很容易被殺掉 , 然后又被他們的父進(jìn)程或者其他的應(yīng)用所拉起來 , 導(dǎo)致陷入了一種死循環(huán) . 對系統(tǒng) CPU \ Memory \ IO 等資源的影響非常大.
比如下面就是一次 Monkey 之后的結(jié)果 , QQ 在短時(shí)間內(nèi)頻繁被殺和重啟 .
07-23 14:32:16.969 1435 3420 I am_proc_bound: [0,30387,com.tencent.mobileqq] 07-23 14:32:16.979 1435 3420 I am_kill : [0,30387,com.tencent.mobileqq,901,empty #3] 07-23 14:32:16.996 1435 3420 I am_proc_died: [0,30387,com.tencent.mobileqq,901,18] 07-23 14:32:17.028 1435 1510 I am_proc_start: [0,30400,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.054 1435 3420 I am_proc_bound: [0,30400,com.tencent.mobileqq] 07-23 14:32:17.064 1435 3420 I am_kill : [0,30400,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.082 1435 3420 I am_proc_died: [0,30400,com.tencent.mobileqq,901,18] 07-23 14:32:17.114 1435 1510 I am_proc_start: [0,30413,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.139 1435 3420 I am_proc_bound: [0,30413,com.tencent.mobileqq] 07-23 14:32:17.149 1435 3420 I am_kill : [0,30413,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.166 1435 3420 I am_proc_died: [0,30413,com.tencent.mobileqq,901,18] 07-23 14:32:17.202 1435 1510 I am_proc_start: [0,30427,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.216 1435 3420 I am_proc_bound: [0,30427,com.tencent.mobileqq] 07-23 14:32:17.226 1435 3420 I am_kill : [0,30427,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.249 1435 3420 I am_proc_died: [0,30427,com.tencent.mobileqq,901,18] 07-23 14:32:17.278 1435 1510 I am_proc_start: [0,30440,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.299 1435 3420 I am_proc_bound: [0,30440,com.tencent.mobileqq] 07-23 14:32:17.309 1435 3420 I am_kill : [0,30440,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.329 1435 2116 I am_proc_died: [0,30440,com.tencent.mobileqq,901,18] 07-23 14:32:17.362 1435 1510 I am_proc_start: [0,30453,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.387 1435 2116 I am_proc_bound: [0,30453,com.tencent.mobileqq] 07-23 14:32:17.398 1435 2116 I am_kill : [0,30453,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.420 1435 2116 I am_proc_died: [0,30453,com.tencent.mobileqq,901,18] 07-23 14:32:17.447 1435 1510 I am_proc_start: [0,30466,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.474 1435 2116 I am_proc_bound: [0,30466,com.tencent.mobileqq] 07-23 14:32:17.484 1435 2116 I am_kill : [0,30466,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.507 1435 2116 I am_proc_died: [0,30466,com.tencent.mobileqq,901,18] 07-23 14:32:17.533 1435 1510 I am_proc_start: [0,30479,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.556 1435 2116 I am_proc_bound: [0,30479,com.tencent.mobileqq] 07-23 14:32:17.566 1435 2116 I am_kill : [0,30479,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.587 1435 2116 I am_proc_died: [0,30479,com.tencent.mobileqq,901,18] 07-23 14:32:17.613 1435 1510 I am_proc_start: [0,30492,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq] 07-23 14:32:17.636 1435 2116 I am_proc_bound: [0,30492,com.tencent.mobileqq] 07-23 14:32:17.646 1435 2116 I am_kill : [0,30492,com.tencent.mobileqq,901,empty #3] 07-23 14:32:17.667 1435 2116 I am_proc_died: [0,30492,com.tencent.mobileqq,901,18]其對應(yīng)的 Systrace - SystemServer 中可以看到 AM 在頻繁殺 QQ 和起 QQ
QQ 頻繁被殺和啟動此 Trace 對應(yīng)的 CPU 部分也可以看到繁忙的 cpu
CPU 繁忙影響內(nèi)存分配和觸發(fā) IO
手機(jī)經(jīng)過長時(shí)間老化使用整機(jī)卡頓一下 , 或者整體比剛剛開機(jī)的時(shí)候操作要慢 , 可能是因?yàn)橛|發(fā)了內(nèi)存回收或者 block io , 而這兩者又經(jīng)常有關(guān)聯(lián) . 內(nèi)存回收可能觸發(fā)了 fast path 回收 \ kswapd 回收 \ direct reclaim 回收 \ LMK 殺進(jìn)程回收等。(fast path 回收不進(jìn)行回寫)
回收的內(nèi)容是匿名頁 swapout 或者 file-backed 頁寫回和清空。(假設(shè)手機(jī)都是 swap file 都是內(nèi)存,不是 disk), 涉及到 file 的,都可能操作 io,增加 block io 的概率。
還有更常見的是打開之前打開過的應(yīng)用,沒有第一次打開的快,需要加載或者卡一段時(shí)間 . 可能發(fā)生了 do_page_fault,這條路徑經(jīng)常見到 block io 在 wait_on_page_bit_killable(),如果是 swapout 內(nèi)存,就要 swapin 了。如果是普通文件,就要 read out in pagecache/disk.
do_page_fault —> lock_page_or_retry -> wait_on_page_bit_killable 里面會判斷 page 是否置位 PG_locked, 如果置位就一直阻塞, 直到 PG_locked 被清除 , 而 PG_locked 標(biāo)志位是在回寫開始時(shí)和 I/O 讀完成時(shí)才會被清除,而 readahead 到 pagecache 功能也對 block io 產(chǎn)生影響,太大了增加阻塞概率。
實(shí)例
下面這個(gè) Trace 是低內(nèi)存情況下 ,抓取的一個(gè) App 的冷啟動 ,我們只取應(yīng)用啟動到第一幀顯示的部分 ,總耗時(shí)為 2s ??梢钥吹狡?Running 的總時(shí)間是 682 ms ,
低內(nèi)存的啟動情況
低內(nèi)存情況下 , 這個(gè) App 從 bindApplication 到第一幀顯示 , 共花費(fèi)了 2s . 從下面的 Thread 信息那里可以看到
Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 這兩欄總共花費(fèi) 750 ms 左右(對比下面正常情況才 130 ms)
Running 的時(shí)間在 600 ms (對比下面正常情況才 624 ms , 相差不大)
從這段時(shí)間內(nèi)的 CPU 使用情況來看 , 除了 HeapTaskDeamon 跑的比較多之外 , 其他的內(nèi)存和 io 相關(guān)的進(jìn)程也非常多 , 比如若干個(gè) kworker 和 kswapd0.
高 IO 場景的 CPU 使用情況正常內(nèi)存情況下
正常內(nèi)存情況下 , 這個(gè) App 從 bindApplication 到第一幀顯示 , 只需要 1.22s . 從下面的 Thread 信息那里可以看到
Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 這兩欄總共才 130 ms.
Running 的時(shí)間是 624 ms
從這段時(shí)間內(nèi)的 CPU 使用情況來看 , 除了 HeapTaskDeamon 跑的比較多之外 , 其他的內(nèi)存和 io 相關(guān)的進(jìn)程非常少.
正常情況啟動的 CPU 使用情況可能的優(yōu)化方案 (來自實(shí)際的經(jīng)驗(yàn)和大佬分享的經(jīng)驗(yàn))
下面列舉的只是一些經(jīng)驗(yàn)之談 , 具體問題還是得具體分析 , 在 Android 平臺上 , 對三方應(yīng)用的管控是非常重要的 , 很多小白用戶 , 一大堆常駐通知和后臺服務(wù) , 導(dǎo)致這些 App 的優(yōu)先級非常高 , 很難被殺掉 . 導(dǎo)致整機(jī)的內(nèi)存長時(shí)間比較低 . 所以做系統(tǒng)的必要的優(yōu)化之后 , 就要著重考慮對三方應(yīng)用的查殺和管控邏輯 , 盡量減少后臺進(jìn)程的個(gè)數(shù) , 在必要的時(shí)候 , 清理掉無用的進(jìn)程來釋放內(nèi)存?zhèn)€前臺應(yīng)用使用.
提高 extra_free_kbytes 值
提高 disk I/O 讀寫速率,如用 UFS3.0,用固態(tài)硬盤
避免設(shè)置太大的 read_ahead_kb 值
使用 cgroup 的 blkio 來限制后臺進(jìn)程的 io 讀操作,縮短前臺 io 響應(yīng)時(shí)間
提前做內(nèi)存回收的操作,避免在用戶使用應(yīng)用時(shí)碰到而感受到稍微卡頓
增加 LMK 效率,避免無效的 kill
kswapd 周期性回收更多的 high 水位
調(diào)整 swappiness 來平衡 pagecache 和 swap
策略 : 針對低內(nèi)存機(jī)器做特殊的策略 , 比如殺進(jìn)程更加激進(jìn) (這會帶來用戶體驗(yàn)的降低 , 所以這個(gè)度需要兼顧性能和用戶體驗(yàn))
策略 : 在內(nèi)存不足的時(shí)候提醒用戶(或者不提醒用戶) , 殺掉不必要的后臺進(jìn)程?
策略 : 在內(nèi)存嚴(yán)重不足且無法恢復(fù)的情況下 , 可以提示用戶重啟手機(jī).
參考文章如下:
android系統(tǒng)優(yōu)化時(shí) systrace 中wait_on_page_locked_killable阻塞過長[6]
線程被IO卡住的原因[7]
參考資料
[1]
Android 中的卡頓丟幀原因概述 - 方法論: https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/
[2]Android 中的卡頓丟幀原因概述 - 系統(tǒng)篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/
[3]Android 中的卡頓丟幀原因概述 - 應(yīng)用篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/
[4]Android 中的卡頓丟幀原因概述 - 低內(nèi)存篇: https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/
[5]/PROC/MEMINFO 之謎: http://linuxperf.com/?p=142
[6]android系統(tǒng)優(yōu)化時(shí) systrace 中wait_on_page_locked_killable阻塞過長: https://blog.csdn.net/qkhhyga2016/article/details/79540119
[7]線程被IO卡住的原因: https://blog.csdn.net/zsj100213/article/details/82427527
總結(jié)
以上是生活随笔為你收集整理的Android 中的卡顿丢帧原因概述 - 低内存篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机文化宣传普及知识展,浅谈计算机文化
- 下一篇: android 百度地图 itemize