在Android 中的卡顿丢帧原因概述 - 系统篇 这篇文章中 , 实际案例这里我们有列举一些由于系统低内存导致的卡顿 , 由于 Android 低内存对整机性能影响比较大 , 所以单独写一篇文章 , 来概述系统低内存对整机性能的影响 .
随着 Android 系统版本的更迭 , 以及 App 的代码膨胀 , Android 系统对内存的需求越来越大 , 但是目前市面上还存在着大量的 4G 内存以下的机器 , 这部分用户就很容易遇到整机低内存的情况 , 尤其是在系统大版本更新和 App 越装越多的情况下 .
Android 低内存会导致性能问题 , 具体表现就是响应慢和卡顿 . 比如启动一个应用要花比平时更长的时间 ; 滑动列表会掉更多帧 ; 后台的进程减少导致冷启动变多 ; 手机很容易发热发烫等 , 下面我会概述发生这些性能问题的原因 . Debug 的方法 , 以及可能的优化措施 .
0. Android 中的卡顿丢帧原因概述 - 方法论
1. Android 中的卡顿丢帧原因概述 - 系统篇
2. Android 中的卡顿丢帧原因概述 - 应用篇
3. Android 中的卡顿丢帧原因概述 - 低内存篇
低内存的数据特征和行为特征
Meminfo 信息
最简单的方法是使用 Android 系统自带的 Dumpsys meminfo 工具
1 | adb shell dumpsys meminfo |
如果系统处于低内存的话 , 会有如下特征:
- FreeRam 的值非常少 , Used RAM 的值非常大
- ZRAM 使用率非常高(如果开了 Zram 的话)
LMK && kswapd 线程活跃
低内存的时候, LKMD 会非常活跃, 在 Kernel Log 里面可以看到 LMK 杀进程的信息:
1 | [kswapd0] lowmemorykiller: Killing 'u.mzsyncservice' (15609) (tgid 15609), adj 906, |
上面这段 Log 的意思是说, 由于 mem 低于我们设定的 900 的水位线 (261272kB),所以把 pid 为 15609 的 mzsyncservice 这个进程杀掉(这个进程的 adj 是 906 )
proc/meminfo
这里是 Linux Kernel 展示 meminfo 的地方 , 关于 meminfo 的解读,可以参考这篇文章:/PROC/MEMINFO之谜
从结果来 , 当系统处于低内存的情况时候 , MemFree 和 MemAvailable 的值都很小
1 | MemTotal: 5630104 kB |
整机卡顿 && 响应慢
低内存的时候,整机使用的时候要比非低内存的时候要卡很多,点击应用或者启动 App 都会有不顺畅或者响应慢的感觉
低内存对性能的具体影响
影响主线程 IO 操作
主线程出现大量的 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 , 导致整个操作的时间变长
出现 CPU 竞争
低内存会触发 Low Memory Killer 进程频繁进行扫描和杀进程,kswapd0 是一个内核工作线程,内存不足时会被唤醒,做内存回收的工作。 当内存频繁在低水位的时候,kswapd0 会被频繁唤醒,占用 cpu ,造成卡顿和耗电。
比如下面这个情况, kswapd0 占用了 855 的超大核 cpu7 ,而且是满频在跑,耗电可想而知,如果此时前台应用的主线程跑到了 cpu7 上,很大可能会出现 cpu 竞争,导致调度不到而丢帧。
HeapTaskDaemon 通常也会在低内存的时候跑的很高
, 来做内存相关的操作
进程频繁查杀和重启
对 AMS 的影响主要集中在进程的查杀上面 , 由于 LMK 的介入 , 处于 Cache 状态的进程很容易被杀掉 , 然后又被他们的父进程或者其他的应用所拉起来 , 导致陷入了一种死循环 . 对系统 CPU \ Memory \ IO 等资源的影响非常大.
比如下面就是一次 Monkey 之后的结果 , QQ 在短时间内频繁被杀和重启 .
1 | 07-23 14:32:16.969 1435 3420 I am_proc_bound: [0,30387,com.tencent.mobileqq] |
其对应的 Systrace - SystemServer 中可以看到 AM 在频繁杀 QQ 和起 QQ
此 Trace 对应的 Kernel 部分也可以看到繁忙的 cpu
影响内存分配和触发 IO
手机经过长时间老化使用整机卡顿一下 , 或者整体比刚刚开机的时候操作要慢 , 可能是因为触发了内存回收或者 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
- 策略 : 针对低内存机器做特殊的策略 , 比如杀进程更加激进 (这会带来用户体验的降低 , 所以这个度需要兼顾性能和用户体验)
- 策略 : 在内存不足的时候提醒用户(或者不提醒用户) , 杀掉不必要的后台进程 .
- 策略 : 在内存严重不足且无法恢复的情况下 , 可以提示用户重启手机.
参考资料
- https://blog.csdn.net/qkhhyga2016/article/details/79540119
- https://blog.csdn.net/zsj100213/article/details/82427527
本文知乎地址
由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面
知乎 - Android 中的卡顿丢帧原因概述 - 低内存篇
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球 : 欢迎加入,多谢支持~
一个人可以走的更快 , 一群人可以走的更远