/dev/shm 写满会触发 Java OOM,因 NIO 的 MappedByteBuffer 依赖 tmpfs 映射,空间耗尽导致 mmap() 失败,抛出 OutOfMemoryError: Map failed;需用 df、ls、dmesg 排查,通过修改 /etc/fstab 永久扩容,并清理残留映射文件。

为什么 /dev/shm 写满会触发 Java OOM?
Java(尤其是 Tomcat)在使用 NIO 时,java.nio.MappedByteBuffer 默认通过 tmpfs 映射文件到内存,底层走的就是 /dev/shm。这个目录本质是内存挂载的 tmpfs 文件系统,大小默认通常只有 64MB(取决于内核版本和发行版)。一旦多个应用或大文件映射反复申请,/dev/shm 耗尽,mmap() 系统调用失败,JVM 就会抛出 OutOfMemoryError: Map failed —— 这不是堆内存不足,而是虚拟内存映射空间被占满。
如何查看当前 /dev/shm 实际大小和使用量?
直接运行命令确认现状,避免盲目调参:
df -h /dev/shm ls -l /dev/shm/ | wc -l # 查看有多少个映射文件(如 tomcat-*.tmp、hsperfdata_*)
常见误判点:看到 df 显示“已用 0”不代表安全——tmpfs 的 inodes 和实际 mmap 区域可能已耗尽,需结合 dmesg | grep -i "shm" 看是否有 Cannot allocate memory 或 Failed to allocate page 日志。
永久调整 /dev/shm 大小的正确方式
不能只改 mount 命令临时生效,Tomcat 启动早于手动 remount,必须确保开机即加载。修改 /etc/fstab 是唯一可靠路径:
立即学习“Java免费学习笔记(深入)”;
- 备份原 fstab:
cp /etc/fstab /etc/fstab.bak - 找到原有
/dev/shm行(可能没有,或为tmpfs /dev/shm tmpfs defaults 0 0) - 替换为(例如设为 2GB):
tmpfs /dev/shm tmpfs size=2g,mode=1777,nr_inodes=50k 0 0 - 执行
mount -o remount /dev/shm立即生效,再df -h /dev/shm验证
注意:size= 必须带单位(k/m/g),mode=1777 保证权限兼容 Java 进程,nr_inodes 防止大量小映射文件耗尽 inode(尤其高并发 Tomcat 场景)。
Tomcat 层面还能做什么来降低风险?
调大 /dev/shm 是治本,但加一层防护更稳妥。重点关两个 JVM 参数和一个 Tomcat 配置:
- 禁用不必要的文件映射:启动参数加
-Dsun.nio.PageAlignDirectBuffers=false(减少对齐开销) - 限制 NIO 缓冲区复用范围:在
server.xml的Connector中显式设置maxSwallowSize="-1"(防止请求体过大触发隐式 mmap) - 避免日志框架写入
/dev/shm:检查 Logback 或 Log4j2 是否配置了AsyncAppender+RingBuffer,某些旧版本会意外使用 tmpfs;升级到 Logback 1.4+ 可规避
真正麻烦的从来不是调参本身,而是排查时发现 /dev/shm 下残留着几天前 Tomcat 意外退出后没清理的 hsperfdata_* 或 tomcat.*.tmp 文件——它们不占 df 统计空间,但持续锁住内存页,必须 rm -f /dev/shm/hsperfdata_* /dev/shm/tomcat.*.tmp 才能释放。










