← 返回首页

几行配置给内存扩容一半:我用 zram 治好了老卡死的机器

编程·约 7 分钟·#Linux#zram#内存#Ubuntu

我这台机器 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 做压缩和解压,而这正是我富余的资源。

zram 换出/换入数据通路
图 1. zram 作为 swap 时一页内存换出/换入的数据通路,全程在内存内完成。

几个要点:

  • 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-fractionmax-zram-size,没有 zram-sizezram-sizeram / 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,是不是平时就吃掉了我一大块内存?答案是不会。先看一眼平时空闲时的状态:

空闲时的内存与 swap
图 3. 内存富裕时(占用 37%)swap 用量为 0——zram 已就位但还没被用到,此刻几乎不占物理内存。图中 Swap 总量 18.8 G = zram 15.5 GiB + 磁盘 swapfile 2 G。

两个要点:

  • 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 与 zswap 的结构对比
图 2. zram 自成终点,压缩数据常驻内存;zswap 是磁盘 swap 前的压缩缓存,池满后向磁盘回写。

zram 是一块独立的内存盘,不需要后备磁盘,压缩数据一直在内存里,填满且没有别的 swap 兜底时只能触发 OOM。zswap 是磁盘 swap 前面的一道压缩缓存,必须配合磁盘 swap:页先压进内存池,池满时按冷热把最旧的页解压、写回磁盘。

维度zramzswap
是否需要后备磁盘不需要需要真实 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 是缓冲,不是无中生有。

参考文献

  1. zram: Compressed RAM-based block devices — The Linux Kernel documentation
  2. systemd/zram-generator
  3. zram — ArchWiki
  4. Chris Down, "Debunking zswap and zram myths" (2026)
  5. zswap — Wikipedia