在Linux系统中,/dev/shm是基于tmpfs实现的内存文件系统,常被用作进程间通信或高速临时读写区域。当Tomcat或Java应用将临时文件、上传缓存、会话数据写入该分区,而tmpfs默认大小仅为物理内存的一半且无法动态扩展时,空间很快被占满。此时JVM申请直接内存或本地线程栈失败,就会抛出OutOfMemoryError,这类OOM往往不伴随堆内存报警,排查起来容易误判为代码泄漏。

一、tmpfs与/dev/shm的基本机制
tmpfs是一种将数据存储在虚拟内存中的文件系统,它不会写入持久化块设备,读写性能极高,但所占空间会计入内存和swap的使用量。系统启动时,initramfs或systemd会根据内核参数挂载/dev/shm,默认大小为物理内存的一半,可通过df -h /dev/shm查看。
由于tmpfs不具备磁盘后备存储,一旦写入量超过挂载时指定的size,内核会拒绝新的写入并返回ENOSPC错误。Java的FileChannel、RandomAccessFile在收到该错误后,某些第三方库会包装为OOM异常,尤其是使用堆外内存映射文件(MappedByteBuffer)时,表面看像是内存不足,实际是tmpfs配额耗尽。
1.1 查看当前挂载属性
使用以下命令可确认/dev/shm的实际限制与使用情况:
# 查看挂载信息 mount | grep shm # 查看空间占用 df -h /dev/shm # 查看系统内存 free -m
若输出中显示size等于内存一半,且Used接近Size,即可判定写满风险。很多运维在排查Tomcat假死时忽略该点,反复调大-Xmx反而让问题更隐蔽。
二、调整tmpfs大小限制的三种方法
解决/dev/shm写满引发OOM的核心思路是合理扩大tmpfs上限,并隔离Java临时目录。下面分别介绍临时调整、持久化配置与JVM层规避。
2.1 使用mount命令临时重挂
在不重启系统的前提下,可重新挂载/dev/shm并指定更大size。该操作立即生效,但重启后失效,适合应急。
# 重新挂载为内存的75% mount -o remount,size=75% /dev/shm # 验证 df -h /dev/shm
这种方式的优点是无需停止Tomcat,缺点是若机器重启,配置回退。在临时促销或突发流量时,先这样扩容争取时间,再规划持久方案。
2.2 通过systemd持久化配置
主流发行版使用systemd管理tmp.mount单元,编辑对应配置文件可让size永久生效。
# 复制模板到etcd cp /usr/share/systemd/tmp.mount /etc/systemd/system/tmp.mount # 修改Options行,例如设为2G # [Mount] # What=tmpfs # Where=/dev/shm # Type=tmpfs # Options=size=2G,nr_inodes=4k systemctl daemon-reload systemctl restart tmp.mount
配置后每次启动都会按设定挂载。需注意size不要超过物理内存与swap总和,否则在内存紧张时引发系统级OOM killer。建议结合监控告警,当/dev/shm使用率超百分之八十时自动通知。
2.3 JVM临时目录隔离
Java默认将java.io.tmpdir指向/dev/shm的情况多见于容器环境。可在Tomcat启动脚本中显式指定到其他磁盘路径,避免临时文件挤占tmpfs。
# 在catalina.sh的JAVA_OPTS中加入 JAVA_OPTS="$JAVA_OPTS -Djava.io.tmpdir=/var/tomcat_tmp"
这样上传文件、编译JSP的临时class都不会写入内存分区。配合前面tmpfs扩容,形成双保险。若应用必须利用tmpfs加速,则应单独建一个受限子目录并配额管理。
三、风险对比与选型建议
不同方案在安全性与运维成本上有明显差异,下面用表格归纳。
| 方案 | 生效范围 | 重启保持 | 风险点 |
|---|---|---|---|
| mount remount | 立即全局 | 否 | 重启回退,应急用 |
| systemd tmp.mount | 开机挂载 | 是 | 配置错误致启动失败 |
| java.io.tmpdir改路径 | 单应用 | 是 | 失去内存加速优势 |
对于稳定性优先的生产集群,推荐systemd持久化配合JVM目录隔离。开发测试环境可用临时重挂快速验证。无论哪种方式,都应把/dev/shm监控纳入Prometheus或Zabbix,防止隐性写满。
四、常见误区澄清
有观点认为tmpfs写满只会删文件就好,不必调大小。实际上高并发下删除本身也需内存操作,且某些锁文件被占用无法释放,OOM仍会复现。还有人把/dev/shm当成永久存储,这是概念错误,tmpfs断电即失,不能替代磁盘。
正确做法是:明确临时数据生命周期,给tmpfs设合理上限,并将关键应用临时目录移出内存分区。
通过上述方法,Tomcat与Java进程因/dev/shm耗尽而产生的OOM可被系统性规避,而不是靠频繁重启掩盖问题。