ZRAM Swap 原理详解
前言
有人看到 “zram 是用内存做 swap” 时会有疑惑:用内存做 swap?那不是用内存存内存,死循环了吗?
这篇文章用最直白的语言解释 zram 的原理、和磁盘 swap 的区别,以及为什么它不是 “死循环”。
Swap 是什么
Swap(交换空间)是操作系统的一种内存管理机制:
- 物理内存不够时,内核把不常用的内存页挪到 swap 空间
- 需要时,再从 swap 空间读回来
- 本质是用空间换时间——牺牲一点速度,换取更多可用内存
磁盘 Swap(传统方式)
应用程序 → 物理内存(RAM)
│ 内存不足时
▼
磁盘(SSD/HDD)
特点:
• 存储介质:SSD 或 HDD
• 速度:慢(毫秒级 I/O 延迟)
• 容量:大(取决于磁盘剩余空间)
• CPU 开销:无
• 适合场景:物理内存严重不足时的兜底方案
ZRAM Swap(压缩内存)
zram 不是 “用内存来存内存”,而是:
核心原理
正常情况(无 zram):
应用程序 → 占用物理内存 100MB
内存压力大时(有 zram):
应用程序 → 占用物理内存 50MB(常用数据保留)
zram 设备 → 压缩存储 50MB 冷数据
↓ 压缩(通常 2:1 ~ 4:1)
实际只占 15MB 物理内存
总计占用:50MB + 15MB = 65MB
相比原来:节省了 35MB
关键点
- 按需分配:zram 不是预先划走一块固定内存,而是按需压缩页面
- 压缩存储:数据在物理内存中,但以压缩形式存放,占用更少空间
- 不是 “用箱子装箱子”:那 15MB 是直接从可用内存池里分配的,是实实在在的物理内存,不存在循环
类比:真空压缩袋
你有一个行李箱(物理内存):
正常情况下:
放 5 件羽绒服 → 占满整个箱子(100%)
用了 zram 压缩袋后:
放 5 件羽绒服 → 用压缩袋抽真空
→ 只占箱子一半空间(50% 压缩比)
→ 还能再放 5 件进去
不是 "拿一个箱子装另一个箱子",
而是 "把衣服压缩变小,省出空间放更多东西"。
磁盘 Swap vs ZRAM 对比
| 特性 | 磁盘 Swap | ZRAM Swap |
|---|---|---|
| 存储介质 | SSD/HDD | 物理内存(压缩后) |
| 读写速度 | 慢(ms 级) | 快(μs 级) |
| 容量 | 大(取决于磁盘) | 小(通常设内存的 50%) |
| CPU 开销 | 无 | 有(压缩/解压) |
| 重启后数据 | 保留 | 丢失 |
| 压缩比 | 不压缩 | 2:1 ~ 4:1 |
| 适合场景 | 内存极度不足 | 内存轻度不足,提高利用率 |
性能实测数据(参考)
操作 延迟
─────────────────────────────
内存直接访问 ~100ns
ZRAM 压缩/解压 ~1-5μs
SSD Swap 读写 ~100μs-1ms
HDD Swap 读写 ~10-20ms
ZRAM 比 SSD swap 快 100-1000 倍。
为什么不是死循环
关键问题:zram 的压缩数据也占物理内存,这部分内存从哪里来?
答案:从可用内存池中来。
系统启动时:
┌────────────────────────────────────┐
│ 物理内存 32GB │
├────────────────────────────────────┤
│ 应用程序 │ 可用内存 │
│ 12GB │ 20GB │
└──────────────┴─────────────────────┘
设置 16GB zram swap(不预分配):
┌────────────────────────────────────┐
│ 物理内存 32GB │
├────────────────────────────────────┤
│ 应用程序 │ zram │ 可用内存 │
│ 12GB │ (按需) │ 20GB │
└──────────────┴────────┴────────────┘
内存压力时,内核把冷数据压缩到 zram:
占用的是那 20GB 可用内存的一小部分
如果可用内存为 0,zram 也无法再分配新页面
→ 系统 OOM 或使用磁盘 swap 兜底
这就好比: 你用手机拍了一张 10MB 的照片,发微信时微信会自动压缩成 1MB 再发送。压缩后的 1MB 还是存在手机内存(存储空间)里的,不是 “用存储存存储” 的死循环。
实际配置示例
#!/bin/bash
# 配置 4GB zram swap(适合 8GB 内存的机器)
ZRAM_SIZE=$((4 * 1024 * 1024 * 1024)) # 4GB
# 添加 zram 设备
ZRAM_DEV=$(cat /sys/class/zram-control/hot_add)
# 设置大小(使用 lz4 算法,速度优先)
echo lz4 > /sys/block/zram${ZRAM_DEV}/comp_algorithm
echo ${ZRAM_SIZE} > /sys/block/zram${ZRAM_DEV}/disksize
# 启用 swap(优先级 100,比磁盘 swap 优先)
mkswap /dev/zram${ZRAM_DEV}
swapon -p 100 /dev/zram${ZRAM_DEV}
查看 zram 使用情况:
# 压缩比、实际占用内存等
cat /sys/block/zram*/stat
# 或使用 zramctl
zramctl
输出示例:
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS
/dev/zram0 lz4 4G 2.1G 987M 1002M 4
↑ ↑ ↑
原始数据 压缩后 含元数据
总结
- zram 不是死循环 — 它只是把冷数据压缩存放,压缩后的数据占用的还是物理内存,但占得更少
- 比磁盘 swap 快很多 — 因为数据在内存中,只是经过了压缩/解压,不需要走 I/O
- 适合桌面/开发机 — 内存不是完全不够,但想提高利用率
- 不适合内存已经完全不够的场景 — 那时必须用磁盘 swap 兜底
理解 zram 的关键就一句话:
“不是用箱子装箱子,而是把衣服压缩了再放回箱子。”
/dev/shm 详解
前言
/dev/shm 是 Linux 下一个容易被忽视但非常实用的特殊文件系统。很多人用过它但不一定清楚它的原理和限制。
什么是 /dev/shm
/dev/shm 是一个 tmpfs(临时文件系统),挂载在内存上的虚拟文件系统。
/dev/shm 的本质:
• 类型:tmpfs(temporary file system)
• 存储介质:物理内存(RAM)+ swap
• 挂载点:/dev/shm
• 默认大小:物理内存的 50%
• 持久性:重启后数据丢失(volatile)
查看当前系统的 /dev/shm:
$ df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
tmpfs 6.3G 0 6.3G 0% /dev/shm
$ mount | grep /dev/shm
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,relatime,inode64)
和 ZRAM 的关系
/dev/shm 和 zram 是两个完全不同的概念,但容易混淆:
| 特性 | /dev/shm (tmpfs) |
ZRAM |
|---|---|---|
| 类型 | 文件系统 | 块设备 |
| 用途 | 共享内存、临时文件 | swap 空间 |
| 数据是否压缩 | 否(原始数据) | 是(压缩存储) |
| 是否占用内存 | 是 | 是(但压缩后更小) |
| 是否可以设置为 swap | 可以(但没必要) | 专门做 swap |
一个简单区分:
/dev/shm → "把内存当成硬盘用"(文件系统)
zram → "把内存当成 swap 用"(块设备 + 压缩)
谁在使用 /dev/shm
POSIX 共享内存(shm_open)
$ ls -la /dev/shm/
-rw------- 1 bill bill 1048576 May 27 18:30 my_shared_mem
Chrome/Chromium 浏览器
Chrome 使用 /dev/shm 作为共享内存缓存,加速多进程通信。
# Chrome 的共享内存文件
$ ls /dev/shm/
com.google.Chrome.*
如果 /dev/shm 太小,Chrome 可能会报 “共享内存不足” 的警告。
Docker/BuildKit
docker/build-push-action 的 --shm-size 参数就是设置 build 容器的 /dev/shm 大小:
- name: Build
uses: docker/build-push-action@v6
with:
shm-size: 2g # 设置 /dev/shm 为 2GB
某些编译工具(如 Go 的编译器、LLVM)在并行编译时会大量使用共享内存,默认的 64MB 可能不够。
Python multiprocessing
Python 的 multiprocessing 模块默认使用 /dev/shm 做共享内存:
from multiprocessing import shared_memory
# 创建 10MB 共享内存块
shm = shared_memory.SharedMemory(name="my_shm", create=True, size=10*1024*1024)
和 TMPFS 的关系
/dev/shm 是 tmpfs 的一种。其他常见的 tmpfs 挂载点:
$ mount | grep tmpfs
tmpfs on /tmp type tmpfs (rw,nosuid,nodev) # 某些发行版
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
tmpfs on /run type tmpfs (rw,nosuid,nodev)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev)
tmpfs 的特点:
写入 tmpfs 的文件:
✓ 读写速度快(纯内存操作)
✓ 重启后自动清空
✓ 动态分配(用多少占多少,不是预分配)
✗ 受物理内存 + swap 限制
✗ 重启后数据丢失
调整 /dev/shm 大小
临时调整(立即生效)
# 调整为 8GB
sudo mount -o remount,size=8G /dev/shm
# 或调整为物理内存的 60%
sudo mount -o remount,size=60% /dev/shm
永久调整
# 编辑 /etc/fstab
tmpfs /dev/shm tmpfs defaults,noexec,nosuid,size=8G 0 0
查看使用情况
# 最简单的
df -h /dev/shm
# 查看具体哪些文件占用了 /dev/shm
du -sh /dev/shm/*
lsof /dev/shm 2>/dev/null | head -20
/dev/shm vs /tmp 的区别
有些人会把 /tmp 也挂载为 tmpfs,但两者有区别:
/dev/shm |
/tmp(如果挂载为 tmpfs) |
|
|---|---|---|
| 用途 | 共享内存(进程间通信) | 临时文件 |
| 标准 | POSIX 标准(shm_open) | 无标准 |
| 清理策略 | 手动删除或重启清除 | 通常自动清理(如 systemd tmpfiles) |
| 固定挂载点 | 是 | 不是 |
不建议把 /tmp 换成 tmpfs 的原因:
如果 /tmp 是 tmpfs(纯内存):
1. 大文件下载到 /tmp 会占满内存
2. 系统意外重启会丢失 /tmp 下的数据
3. 某些程序依赖 /tmp 持久性(如 /var/tmp)
更好的做法:
/tmp → 磁盘文件系统(如 ext4/xfs)
/dev/shm → tmpfs(只用于共享内存)
/dev/shm 的常见问题
问题:Chrome 报 “共享内存不足”
症状:Chrome 标签页卡顿,报 "Shared memory" 相关错误
原因:/dev/shm 默认太小(物理内存的 50%),Chrome 多进程通信占满
解决:扩大 /dev/shm
sudo mount -o remount,size=4G /dev/shm
问题:Docker build 失败(OOM)
症状:Docker build 过程中报 "Killed" 或 "Cannot allocate memory"
原因:编译工具(如 Rust 的并行编译)使用了大量共享内存
解决:设置 --shm-size
# docker-compose.yml
services:
build:
build:
context: .
shm_size: '2gb'
问题:tmpfs 数据丢失
症状:重启后 /dev/shm 下的文件消失了
原因:这是正常行为!tmpfs 是 volatile 存储
解决:需要持久化的数据不要放在 /dev/shm
总结
| 概念 | 本质 | 用途 |
|---|---|---|
| ZRAM | 压缩内存块设备 | 提高内存利用率(冷数据压缩) |
/dev/shm |
内存文件系统 | 进程间共享内存 |
| Swap(磁盘) | 磁盘交换空间 | 内存不足时兜底 |
三个概念的关系:
物理内存(RAM)
│
├── 应用程序直接使用(热数据)
│
├── zram(冷数据,压缩后继续存放在内存中)
│ └── 压缩后的数据也占用物理内存
│
├── /dev/shm(共享内存,文件系统方式使用内存)
│
└── swap(磁盘,内存真不够时溢出到磁盘)
└── 磁盘上的 swap 也可能同时存在
一句话总结:
– ZRAM = “把内存压缩,省出空间”
– /dev/shm = “把内存当硬盘,快速读写临时数据”
– 磁盘 Swap = “内存真不够了,用硬盘兜底”
