Half your RAM in a few lines of config: fixing a freezing machine with zram
我这台机器 32G 内存、i7-10700,CPU 大多数时候闲着,但内存一吃紧就卡死。只用几行配置,我把内存的一半划给 zram 当压缩 swap,把用不上的 CPU 换成更耐用的内存。配置时在 Ubuntu 22.04 上踩了一个坑:照网上的新语法写,zram 只给了我 4G。这篇记录从问题、原理、配置到踩坑修复的完整过程。
一、我的问题
我的机器是 Ubuntu 22.04.5、内核 6.8,31 GiB 内存(标称 32G),CPU 是 i7-10700,16 个线程。平时开着浏览器、编辑器和几个容器,内存一冲高就卡死,鼠标都拖不动,要等十几秒才缓过来。但同一时间 CPU 基本是闲的。
先看了内存和 swap:
$ free -h
total used free shared buff/cache available
Mem: 31Gi 8.6Gi 11Gi 909Mi 10Gi 21Gi
Swap: 2.0Gi 0B 2.0Gi
$ swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
只有一个 2 G 的磁盘 swapfile 在兜底。内存一紧,内核就往这块慢盘上倒页,一旦开始来回换页(thrashing),整机就卡住。swappiness 是默认的 60,没有 zram,zswap 也没开。
思路很直接:内存不够、CPU 有余,那就用 CPU 换内存——上 zram。
二、zram 是什么,为什么能换
zram 在内存里建一块块设备,写进去的数据先用压缩算法压一遍再存。把它当 swap 用,内核换出的页就被压缩后留在内存里,而不是写到磁盘。匿名页(进程的堆栈、数据这类没有文件后备的内存)通常能压到原大小的三分之一左右,于是同样的物理内存能装下更多内容。代价是每次换出、换入都要花 CPU 做压缩和解压,而这正是我富余的资源。
几个要点:
- zsmalloc:内核里专门的分配器,把大小不一的压缩块紧凑地打包进内存页,减少碎片,让压缩省下的空间真正变成可用容量。
- 压缩算法:可选
lz4(快、压缩比一般)、zstd(压缩比高、稍慢)等,我用zstd。 - disksize 是逻辑容量:声明 16 G 不代表占 16 G 物理内存,实际占用只跟当前压缩后的数据量成正比。
- same-filled page:整页全是同一字节(最常见是全 0)的页,只记一个标记,几乎不占内存。
三、动手配置,以及我踩的坑
我用 systemd 的 zram-generator:配置驱动,开机自动重建,最省事。
第 1 步,安装。
sudo apt update && sudo apt install -y systemd-zram-generator
第 2 步,写配置。 我照搜到的教程写了下面这版——后面会看到,这版是错的:
# /etc/systemd/zram-generator.conf (错误示范)
[zram0]
zram-size = ram / 2
compression-algorithm = zstd
swap-priority = 100
sudo systemctl daemon-reload
sudo systemctl start systemd-zram-setup@zram0.service
第 3 步,一看结果,不对:
$ zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 zstd 4G 4K 64B 20K 16 [SWAP]
31 G 的一半应该是约 15.5 G,怎么只给了 4 G?而且 zstd 和优先级都对——说明配置文件确实被读了,单单尺寸不对。
先确认配置没写错,文件就是 zram-size = ram / 2,没问题。于是去翻日志:
$ sudo journalctl -b | grep -i 'unknown key'
zram-generator[...]: Unknown key zram-size, ignoring.
原因出来了:它根本不认识 zram-size 这个键,直接忽略了。再看版本和手册:
$ /lib/systemd/system-generators/zram-generator --version
zram-generator 0.3.2
$ man 5 zram-generator.conf | grep -oE 'zram-fraction|max-zram-size|zram-size' | sort -u
max-zram-size
zram-fraction
手册里只有 zram-fraction 和 max-zram-size,没有 zram-size。zram-size(ram / 2 这种表达式语法)是 zram-generator 较新版本(1.x)才有的写法;Ubuntu 22.04 装的是 0.3.2,只认老键。zram-size 被忽略后,它退回到默认值——zram-fraction 默认 0.5、max-zram-size 默认 4096(MB),于是 min(0.5 × 31G, 4G) = 4G。这就是那 4 G 的来历。
坑就坑在它静默退回默认、只在日志里留一行 Unknown key:照抄新版教程、又不去翻 journal 的人,很容易就拿着一块 4 G 的 zram 以为配好了。
第 4 步,改用这版认识的键:
# /etc/systemd/zram-generator.conf (正确)
[zram0]
zram-fraction = 0.5
max-zram-size = 16384
compression-algorithm = zstd
swap-priority = 100
zram-fraction = 0.5 把逻辑容量设成内存的一半;max-zram-size = 16384(16 G)把默认 4 G 的上限抬上去,否则又会被砍回 4 G。重启服务:
sudo systemctl restart systemd-zram-setup@zram0.service
$ zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 zstd 15.5G 4K 64B 20K 16 [SWAP]
$ swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
/dev/zram0 partition 15.5G 0B 100
这次对了:15.5 G、zstd、优先级 100,排在磁盘 swapfile(-2)前面。内核会先用 zram,用满了才轮到磁盘。
第 5 步,提高 swappiness。 zram 换页很便宜,我把 swappiness 调高,让内核更早把冷页压进 zram,而不是死撑物理内存直到颠簸:
echo 'vm.swappiness = 150' | sudo tee /etc/sysctl.d/99-zram.conf
sudo sysctl --system
四、开机自启与持久化
不用额外做什么。zram-generator 是 systemd 生成器,每次开机会按 /etc/systemd/zram-generator.conf 自动重建 zram swap;swappiness 写在 /etc/sysctl.d/99-zram.conf 里也是持久的。重启后 zramctl 直接能看到那块 15.5 G 的 zram0。
五、它平时占内存吗?什么时候才被用到
配完我有个疑问:开了 15.5 G 的 zram,是不是平时就吃掉了我一大块内存?答案是不会。先看一眼平时空闲时的状态:

两个要点:
- zram 不预占内存。
disksize那 15.5 G 是逻辑容量,zram 实际占用的物理内存只跟当前压进去的数据成正比。图里 swap 用量是 0,所以 zram 此刻几乎不吃物理内存——我配出 18.8 G 的 swap 池,并没有让可用内存少一点。 - 它按内存压力随用随取,不是等"满了"才用。内核按压力渐进回收:内存越紧,越会把很久没碰的冷页压进 zram,腾出物理内存给活跃数据。这个过程在远没到 OOM 之前就开始,所以 zram 是个提前介入的缓冲,而不是最后一刻的救命稻草。
swappiness = 150让它在有压力时更倾向于把冷页换进便宜的 zram,而不是死扛物理内存。判断系统有没有压力,最直接就是看 swap 用量:像图里这样接近 0,就说明很宽裕。
要亲眼看它工作,等内存压上去(比如 Memory 涨到 80% 以上)再看这几项:
zramctl # DATA(原始) 会从 0 涨起来,TOTAL 是它实占的物理内存
cat /sys/block/zram0/mm_stat # 更细:orig_data_size compr_data_size mem_used_total ...
swapon --show # /dev/zram0 的 USED 会 > 0
zramctl 的三列是关键:DATA 是写入的原始数据量,COMPR 是压缩后大小,TOTAL 是含分配器开销的实际内存占用,DATA / TOTAL 就是实际压缩比。还有个常见误读:配了 zram 后,监控里 swap 用量会变活跃,这是正常的——这些"swap"并没有离开内存;判断它是否真起作用,看压缩比,而不是 swap 用量数字。
六、zram 和 zswap 的区别
顺带说一下另一个容易混淆的东西 zswap。两者都用压缩省内存,但结构不同。
zram 是一块独立的内存盘,不需要后备磁盘,压缩数据一直在内存里,填满且没有别的 swap 兜底时只能触发 OOM。zswap 是磁盘 swap 前面的一道压缩缓存,必须配合磁盘 swap:页先压进内存池,池满时按冷热把最旧的页解压、写回磁盘。
| 维度 | zram | zswap |
|---|---|---|
| 是否需要后备磁盘 | 不需要 | 需要真实 swap 分区 |
| 压缩数据存放 | 常驻内存 | 内存池,满了写回磁盘 |
| 池满之后 | 触发 OOM | 向磁盘回写溢出 |
| 适用 | swap 需求小且稳定 | swap 需求大或不可预测 |
我的场景是想要一块快的、自给自足的内存 swap,所以选 zram。
七、适用、注意,和实际效果
适合用 zram 的情况就是我这种:物理内存紧、CPU 有余量。
几点注意:
- 不可压缩数据会白耗 CPU。已压缩的媒体、加密数据、随机数据压不动,压缩比接近 1。这类负载可以配
writeback把它们写回磁盘。 - zram 只能推迟 OOM,不能消除 OOM。我保留了默认开着的
systemd-oomd,让它在内存真耗尽前杀掉失控进程,避免硬锁死。 - CPU 满载时会抢算力。zram 的前提是"内存紧、CPU 闲"。我的 CPU 大多数时候闲着,不亏。
实际效果,实话说:zram 不是真的加内存,而是让现有内存靠压缩多装一些、并把 swap 变快。配完之后,内存冲高时不再卡死成那样——以前是往 2 G 磁盘 swap 上颠簸,现在是压进 zram、微秒级就能取回。effective 容量大概多出 8–12 G 量级。但如果工作负载常驻就要超过 32 G,唯一的真解还是加内存条;zram 是缓冲,不是无中生有。