20、内存压缩技术:ZRAM与ZSWAP,Swap与kswapd,内存压力管理
内存不够用了怎么办?
这是Android系统最常遇到的难题。你想想看,手机后台挂着十几个App,前台还在打游戏,内存就那么几个G,系统怎么撑住的?
答案就是——内存压缩与交换。说白了,就是把不常用的数据「挤一挤」,腾出空间给真正需要的人。
20.1 从Swap说起:虚拟内存的基石
Swap这个概念,搞Linux的都不陌生。它就是把内存里暂时不用的数据,挪到磁盘上存着。等要用的时候再读回来。
我早年做嵌入式Linux时,Swap是标配。那时候内存才64MB,没有Swap根本跑不起来。但到了Android上,情况有点不一样——手机用的是闪存,读写速度比机械硬盘快,但跟内存比还是差了一个数量级。
核心问题:Swap的代价是I/O。频繁换入换出,系统就会卡顿。Android对响应速度要求极高,所以不能像服务器那样随便用Swap。
那怎么办?
两个思路:一是让Swap更快(ZRAM),二是让Swap更智能(ZSWAP)。
20.2 ZRAM:内存里的压缩Swap
ZRAM,说白了就是在内存里划出一块区域,专门做压缩。数据先压缩再存进去,需要时解压读出来。
这招妙在哪里?
- 压缩和解压都在内存里完成,没有I/O开销
- 压缩比通常在2:1到3:1之间,相当于白嫖了一倍空间
- 比真正的Swap快得多,延迟在微秒级
我在项目中遇到过一个问题:某款低端机,内存只有2GB,用户反馈多开几个App就杀后台。我们打开ZRAM,给了512MB的压缩空间,后台留存率直接提升了40%。
我的建议:ZRAM的大小不要超过物理内存的50%。太大了反而增加CPU负担,得不偿失。
ZRAM的工作原理其实很简单:
# 查看ZRAM状态
adb shell cat /sys/block/zram0/mm_stat
# 输出示例(单位:字节)
# 原始数据大小 压缩后大小 元数据大小 内存使用 最大使用 压缩比 总写入页数
# 1048576 524288 16384 540672 540672 2.0 256
看到那个压缩比了吗?2.0,意味着原始数据被压缩了一半。这就是ZRAM的价值。
20.3 ZSWAP:更聪明的压缩策略
ZRAM是把所有Swap数据都压缩。但有些数据其实不值得压缩——比如已经被压缩过的图片、视频帧,再压也压不了多少。
ZSWAP就是来解决这个问题的。它只压缩那些「值得压缩」的页面,其他的直接丢给真正的Swap设备。
我打个比方:
- ZRAM像是一个万能压缩袋,什么东西都往里塞
- ZSWAP像是一个智能分拣员,先判断一下,能压的压,不能压的送走
ZSWAP的架构分三层:
- 前端:拦截Swap请求,决定是否压缩
- 压缩池:存放压缩后的页面,类似ZRAM
- 后端:真正的Swap设备(比如闪存分区)
注意:ZSWAP在Android上用得不多。因为大多数手机没有配置传统Swap设备。ZRAM才是主流。但如果你在做内核定制,ZSWAP是个值得研究的选项。
20.4 kswapd:内存压力的守门人
kswapd是内核里的一个内核线程。它的工作就是监控内存使用情况,在内存快不够用的时候,提前做回收和压缩。
它有两个水位线:
| 水位线 | 含义 | 触发动作 |
|---|---|---|
| pages_low | 内存偏低 | kswapd开始异步回收 |
| pages_min | 内存严重不足 | 直接回收(同步,会卡顿) |
| pages_high | 内存充足 | kswapd停止工作 |
我曾经调试过一个性能问题:手机在打开相机时突然卡死。查了半天,发现是kswapd在疯狂回收内存,因为pages_min设置得太低,导致直接回收频繁触发。
避坑指南:我曾经把pages_min设得太高,结果kswapd动不动就干活,CPU占用率飙升。后来调低了一点,反而更流畅。这个值需要根据实际负载来调,没有标准答案。
20.5 内存压力管理:从全局看问题
ZRAM、ZSWAP、kswapd,这些都不是孤立的。它们共同构成了Android的内存压力管理体系。
我画了一张图,帮你理清它们的关系:
整个流程是这样的:
- App申请内存,内核发现不够了
- kswapd被唤醒,开始回收不活跃页面
- 回收的页面先尝试压缩到ZRAM(或ZSWAP)
- 如果ZRAM满了,再写入真正的Swap设备
- 需要时,从ZRAM或Swap设备换入
嗯,这里要注意:ZRAM和ZSWAP不是互斥的。你可以同时用,但Android默认只开了ZRAM。因为大多数用户场景下,ZRAM已经够用了。
20.6 实战:如何监控和调优
说了这么多理论,来点实际的。怎么知道你的手机内存压力大不大?
# 查看内存压力
adb shell cat /proc/pressure/memory
# 输出示例
# some avg10=0.00 avg60=0.00 avg300=0.00 total=12345
# full avg10=0.00 avg60=0.00 avg300=0.00 total=6789
这个文件是内核4.20以后才有的。some表示至少一个进程在等内存,full表示所有进程都在等。数值越高,压力越大。
我的经验:avg10超过10就要警惕了。如果持续超过30,说明内存严重不足,该考虑加ZRAM或者杀后台了。
再看ZRAM的使用情况:
adb shell cat /sys/block/zram0/mm_stat
# 重点关注
# 第1列:原始数据大小(未压缩)
# 第2列:压缩后大小
# 第5列:实际使用的物理内存
# 第6列:压缩比(第1列/第2列)
压缩比低于1.5?说明压缩效果不好。可能是数据本身就不适合压缩,也可能是ZRAM配置有问题。
20.7 总结
内存压缩技术,说白了就是拿CPU换内存。ZRAM用压缩换空间,kswapd用预判换时间。两者配合,让Android在有限的内存里跑得更流畅。
我个人习惯是:低端机开大ZRAM(不超过50%),中高端机开小ZRAM(20%-30%)。因为高端机CPU强,压缩解压快,但内存也大,不需要太多压缩空间。
最后说一句:别迷信ZRAM。它只是缓解内存压力的手段之一。真正的解决方案,还是让App少占内存。嗯,那是另一个话题了。
公众号:蓝海资料掘金营,微信deep3321