/dev/shm 被塞满导致 Redis / PostgreSQL / Java 崩溃的业务场景

10次阅读

/dev/shm 被塞满会直接触发 OOM Killer 或导致进程写入失败,redis、postgresqljava 等依赖共享内存的应用可能静默崩溃或拒绝服务;根本原因是 fork()、shm_open() 等系统调用在 /dev/shm 空间不足时失败,且默认 64MB 容量远低于实际需求,需通过限制大小、隔离路径和显式清理防范。

/dev/shm 被塞满导致 Redis / PostgreSQL / Java 崩溃的业务场景

/dev/shm 被塞满会直接触发 OOM Killer 或导致进程写入失败,redis、PostgreSQL、Java(尤其是使用 tmpfs外缓存或 JNI 共享内存的场景)都可能静默崩溃或拒绝服务——这不是配置问题,是资源耗尽的硬限制。

为什么 /dev/shm 满了会让 Redis 崩溃

Redis 默认使用 fork() 做 RDB 快照,而 linuxfork()/dev/shm 启用 tmpfs 且未设限的情况下,会尝试为写时复制(COW)页表分配共享内存空间。如果 /dev/shm 已满,fork() 返回 -1,Redis 日志里出现 Can't save in background: fork: Cannot allocate memory,接着主进程可能因 BGSAVE 失败重试、OOM 被杀,或降级为只读。

  • /dev/shm 默认大小通常是 64MB(部分发行版为 2GB),远小于 Redis 实际内存占用,极易触顶
  • 即使没开 save 配置,某些模块(如 Redis Modules 使用 malloc + mmap(MAP_SHARED))也会往 /dev/shm 写临时段
  • vm.overcommit_memory=2 下更敏感:内核严格校验可用内存,fork() 失败概率陡增

PostgreSQL 的 shared_buffers 不走 /dev/shm,但 wal_buffers 和 parallel query 可能踩坑

PostgreSQL 主内存不依赖 /dev/shm,但它在启用 huge_pages=on 且系统配置了 hugetlb_shm_group 时,会通过 shmget() 创建大页共享内存段;更常见的是 WAL buffer 刷盘前的临时映射、并行查询 worker 进程间通信(DynamicSharedMemory)——这些路径在高并发写入或并行度 > 10 时,会密集创建/销毁匿名 tmpfs 文件,堆积在 /dev/shm

  • 典型错误:could not resize shared memory segment "/PostgreSQL.XXXXX": No space left on device
  • pg_stat_activity 中大量 parallel worker 状态为 idle in transaction (aborted),实为 shm 分配失败后回滚
  • 检查方法:ls -l /dev/shm | grep PostgreSQL | wc -l,超 500 个临时段就值得警惕

Java 应用(尤其 spring Boot +.netty / JNA)悄悄往 /dev/shm 写东西

多数人以为 Java 只用堆和本地内存,但 Netty 的 EpollEventLoop、JNA 的 NativeLibrary 加载、甚至某些国产 JDK(如 Dragonwell)的 G1ConcRefinementThreads 共享缓冲区,都会调用 shm_open() 创建命名信号量或环形缓冲区,默认落点就是 /dev/shm。这些文件不会自动清理,重启 jvm 也不删——除非显式调用 shm_unlink()

立即学习Java免费学习笔记(深入)”;

  • 现象:java.io.IOException: No space left on device,但 df -h 显示磁盘充足,df -h /dev/shm 却是 100%
  • JVM 参数如 -XX:+UseG1GC 本身不写 /dev/shm,但搭配 io.netty:netty-transport-native-epoll 就会
  • 排查命令:find /dev/shm -name "*java*" -o -name "*netty*" -o -name "*jna*" -ls

怎么安全清理和长期防住

别直接 rm -rf /dev/shm/*——正在使用的 shm 对象被删会导致进程 SigsEGV。必须先识别谁在用,再优雅释放。

  • 查占用者:lsof +D /dev/shm(需 root)或 ipcs -m -p 看 shmid 关联的 pid
  • 清空前先停对应服务:比如 systemctl stop redisfind /dev/shm -name "redis-*" -delete
  • 永久方案不是扩容,而是限制+隔离:
    – 修改 /etc/fstab 行:tmpfs /dev/shm tmpfs defaults,size=1g,mode=1777 0 0(避免默认 64MB)
    – 给关键服务分配独立 shm 目录:mkdir /dev/shm/redis && mount --bind /dev/shm/redis /dev/shm(仅限单实例)
    – Java 应用加启动参数:-Dio.netty.native.workdir=/tmp/netty-shm,避开 /dev/shm

最麻烦的不是清理,是那些没显式关闭 shm 的 C/c++ 扩展或老旧 JNI 库——它们可能只在进程退出时才释放,而 Java 进程又常驻不退。这种 case 必须翻源码确认 shm_unlink 调用点,否则监控再全也拦不住泄漏。

text=ZqhQzanResources