赞
踩
在Android 中的卡顿丢帧原因概述 - 系统篇 这篇文章中 , 实际案例这里我们有列举一些由于系统低内存导致的卡顿 , 由于 Android 低内存对整机性能影响比较大 , 所以单独写一篇文章 , 来概述系统低内存对整机性能的影响 .
随着 Android 系统版本的更迭 , 以及 App 的代码膨胀 , Android 系统对内存的需求越来越大 , 但是目前市面上还存在着大量的 4G 内存以下的机器 , 这部分用户就很容易遇到整机低内存的情况 , 尤其是在系统大版本更新和 App 越装越多的情况下 .
Android 低内存会导致性能问题 , 具体表现就是响应慢和卡顿 . 比如启动一个应用要花比平时更长的时间 ; 滑动列表会掉更多帧 ; 后台的进程减少导致冷启动变多 ; 手机很容易发热发烫等 , 下面我会概述发生这些性能问题的原因 . Debug 的方法 , 以及可能的优化措施 .
Android 中的卡顿丢帧原因概述 - 应用篇[3]
Android 中的卡顿丢帧原因概述 - 低内存篇[4]
最简单的方法是使用 Android 系统自带的 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,863K
- ZRAM: 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)
如果系统处于低内存的话 , 会有如下特征:
FreeRam 的值非常少 , Used RAM 的值非常大
ZRAM 使用率非常高(如果开了 Zram 的话)
低内存的时候, LKMD 会非常活跃, 在 Kernel Log 里面可以看到 LMK 杀进程的信息:
- [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 低于我们设定的 900 的水位线 (261272kB),所以把 pid 为 15609 的 mzsyncservice 这个进程杀掉(这个进程的 adj 是 906 )
这里是 Linux Kernel 展示 meminfo 的地方 , 关于 meminfo 的解读,可以参考这篇文章:/PROC/MEMINFO 之谜[5]
从结果来 , 当系统处于低内存的情况时候 , 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
低内存的时候,整机使用的时候要比非低内存的时候要卡很多,点击应用或者启动 App 都会有不顺畅或者响应慢的感觉
主线程出现大量的 IO 相关的问题 ,
反馈到 Trace 上就是有大量的黄色 Trace State 出现 , 例如 : Uninterruptible Sleep | WakeKill - Block I/O .
查看其 Block 信息 (kernel callsite when blocked:: "wait_on_page_bit_killable+0x78/0x88)
Linux 系统的 page cache 链表中有时会出现一些还没准备好的 page ( 即还没把磁盘中的内容完全地读出来 ) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.
当出现大量的 IO 操作的时候,应用主线程的 Uninterruptible Sleep 也会变多,此时涉及到 io 操作(比如 view ,读文件,读配置文件、读 odex 文件),都会触发 Uninterruptible Sleep , 导致整个操作的时间变长
低内存会触发 Low Memory Killer 进程频繁进行扫描和杀进程,kswapd0 是一个内核工作线程,内存不足时会被唤醒,做内存回收的工作。当内存频繁在低水位的时候,kswapd0 会被频繁唤醒,占用 cpu ,造成卡顿和耗电。
比如下面这个情况, kswapd0 占用了 855 的超大核 cpu7 ,而且是满频在跑,耗电可想而知,如果此时前台应用的主线程跑到了 cpu7 上,很大可能会出现 cpu 竞争,导致调度不到而丢帧。
HeapTaskDaemon 通常也会在低内存的时候跑的很高,来做内存回收相关的操作
对 AMS 的影响主要集中在进程的查杀上面 , 由于 LMK 的介入 , 处于 Cache 状态的进程很容易被杀掉 , 然后又被他们的父进程或者其他的应用所拉起来 , 导致陷入了一种死循环 . 对系统 CPU \ Memory \ IO 等资源的影响非常大.
比如下面就是一次 Monkey 之后的结果 , QQ 在短时间内频繁被杀和重启 .
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]
其对应的 Systrace - SystemServer 中可以看到 AM 在频繁杀 QQ 和起 QQ
此 Trace 对应的 CPU 部分也可以看到繁忙的 cpu
手机经过长时间老化使用整机卡顿一下 , 或者整体比刚刚开机的时候操作要慢 , 可能是因为触发了内存回收或者 block io , 而这两者又经常有关联 . 内存回收可能触发了 fast path 回收 \ kswapd 回收 \ direct reclaim 回收 \ LMK 杀进程回收等。(fast path 回收不进行回写)
回收的内容是匿名页 swapout 或者 file-backed 页写回和清空。(假设手机都是 swap file 都是内存,不是 disk), 涉及到 file 的,都可能操作 io,增加 block io 的概率。
还有更常见的是打开之前打开过的应用,没有第一次打开的快,需要加载或者卡一段时间 . 可能发生了 do_page_fault,这条路径经常见到 block io 在 wait_on_page_bit_killable(),如果是 swapout 内存,就要 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 标志位是在回写开始时和 I/O 读完成时才会被清除,而 readahead 到 pagecache 功能也对 block io 产生影响,太大了增加阻塞概率。
下面这个 Trace 是低内存情况下 ,抓取的一个 App 的冷启动 ,我们只取应用启动到第一帧显示的部分 ,总耗时为 2s 。可以看到其 Running 的总时间是 682 ms ,
低内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 共花费了 2s . 从下面的 Thread 信息那里可以看到
Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 这两栏总共花费 750 ms 左右(对比下面正常情况才 130 ms)
Running 的时间在 600 ms (对比下面正常情况才 624 ms , 相差不大)
从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDeamon 跑的比较多之外 , 其他的内存和 io 相关的进程也非常多 , 比如若干个 kworker 和 kswapd0.
正常内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 只需要 1.22s . 从下面的 Thread 信息那里可以看到
Uninterruptible Sleep | WakeKill - Block I/O 和 Uninterruptible Sleep 这两栏总共才 130 ms.
Running 的时间是 624 ms
从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDeamon 跑的比较多之外 , 其他的内存和 io 相关的进程非常少.
下面列举的只是一些经验之谈 , 具体问题还是得具体分析 , 在 Android 平台上 , 对三方应用的管控是非常重要的 , 很多小白用户 , 一大堆常驻通知和后台服务 , 导致这些 App 的优先级非常高 , 很难被杀掉 . 导致整机的内存长时间比较低 . 所以做系统的必要的优化之后 , 就要着重考虑对三方应用的查杀和管控逻辑 , 尽量减少后台进程的个数 , 在必要的时候 , 清理掉无用的进程来释放内存个前台应用使用.
提高 extra_free_kbytes 值
提高 disk I/O 读写速率,如用 UFS3.0,用固态硬盘
避免设置太大的 read_ahead_kb 值
使用 cgroup 的 blkio 来限制后台进程的 io 读操作,缩短前台 io 响应时间
提前做内存回收的操作,避免在用户使用应用时碰到而感受到稍微卡顿
增加 LMK 效率,避免无效的 kill
kswapd 周期性回收更多的 high 水位
调整 swappiness 来平衡 pagecache 和 swap
策略 : 针对低内存机器做特殊的策略 , 比如杀进程更加激进 (这会带来用户体验的降低 , 所以这个度需要兼顾性能和用户体验)
策略 : 在内存不足的时候提醒用户(或者不提醒用户) , 杀掉不必要的后台进程
策略 : 在内存严重不足且无法恢复的情况下 , 可以提示用户重启手机.
参考文章如下:
android系统优化时 systrace 中wait_on_page_locked_killable阻塞过长[6]
线程被IO卡住的原因[7]
[1]
Android 中的卡顿丢帧原因概述 - 方法论: https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/
[2]Android 中的卡顿丢帧原因概述 - 系统篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/
[3]Android 中的卡顿丢帧原因概述 - 应用篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/
[4]Android 中的卡顿丢帧原因概述 - 低内存篇: https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/
[5]/PROC/MEMINFO 之谜: http://linuxperf.com/?p=142
[6]android系统优化时 systrace 中wait_on_page_locked_killable阻塞过长: https://blog.csdn.net/qkhhyga2016/article/details/79540119
[7]线程被IO卡住的原因: https://blog.csdn.net/zsj100213/article/details/82427527
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。