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的架构分三层:

  1. 前端:拦截Swap请求,决定是否压缩
  2. 压缩池:存放压缩后的页面,类似ZRAM
  3. 后端:真正的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的内存压力管理体系。

我画了一张图,帮你理清它们的关系:

Android内存压力管理体系 App进程 内核内存管理 (MM) kswapd 内核线程 ZRAM 压缩缓存 ZSWAP 智能压缩 Swap设备 (闪存) 物理内存 内存不足通知 压缩存储 智能压缩/转发 溢出 换入 应用 内核 kswapd ZRAM ZSWAP

整个流程是这样的:

  1. App申请内存,内核发现不够了
  2. kswapd被唤醒,开始回收不活跃页面
  3. 回收的页面先尝试压缩到ZRAM(或ZSWAP)
  4. 如果ZRAM满了,再写入真正的Swap设备
  5. 需要时,从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