发布时间:2026-09-17 12:30 更新时间:2026-09-17 12:30 阅读量:0
很多站长都遇到过这种场景:监控突然报警说系统盘使用率 95%,登录服务器执行 df -h 一看确实快满了,可接下来用 du -sh /* 一层层往下数,把所有目录加起来的体积离已用空间差了好几个 GB,怎么也对不上。删了几个日志文件,空间也不见松动。这种情况下,多半不是磁盘真的被什么大文件占着,而是有一批文件已经被删除,但仍有进程握着它们的句柄不肯放手。
理解了这个原理,排查其实不复杂,一条 lsof 命令加上 /proc 目录就能定位到元凶,处理方式也分“温和”和“彻底”两种。下面按原理、定位、回收三步讲清楚,新手照着敲命令即可。
在 Linux 的视角里,磁盘空间是记在 inode 上的,而 inode 什么时候真正释放,取决于还有没有“引用”指向它。平时我们执行 rm,只是把目录项(也就是文件名到 inode 的链接)删掉了,相当于摘掉了路牌。如果此时没有任何进程打开这个文件,inode 的引用计数归零,内核就会把数据块回收,df 立刻能看到空间下降。
但如果某个进程已经用 open() 打开了这个文件,它手里握着一个文件描述符,这个引用就一直存在。你 rm 之后,进程读写依然正常,数据块也不会释放,直到进程关闭该描述符或进程退出,空间才真正归还。日志类文件最容易踩这个坑:Nginx、Tomcat、Java 应用、数据库日志,程序启动时打开文件并长期持有,运维为了救急直接 rm,结果盘还是满的。
更隐蔽的一点是,这类“幽灵文件”在文件系统里已经没有名字了,ls、find、du 都遍历不到它,因为它们都是按目录树走的。所以你会看到 df 说用了 40G,du 只能算出 32G,中间那 8G 就藏在这里。
定位被删除但仍被占用的文件,最直接的工具是 lsof。它的 +L1 参数表示列出链接数小于 1 的已打开文件,也就是被删掉之后还开着的那些。命令如下:
# 列出所有已删除但仍被进程占用的文件(需要 root 权限才能看全)
lsof +L1
只看某个挂载点上的,并按大小排序,方便找大块头
lsof +L1 / | sort -k7 -n -r | head -20
输出的关键列是 COMMAND(进程名)、PID(进程号)、NAME(被删文件的路径,通常带 (deleted) 字样)。注意 lsof 默认只显示文件描述符大小不直观,加 -s 可以看具体尺寸。如果服务器上没装 lsof,或者不想装额外工具,可以直接翻 /proc:
# 遍历所有进程的 fd 目录,找出指向已删除文件的描述符
find /proc/*/fd -ls 2>/dev/null | grep '(deleted)' | head -20
统计每个进程持有的已删除文件总大小(单位:字节)
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
size=$(ls -l /proc/$pid/fd 2>/dev/null | grep '(deleted)' | awk '{s+=$5} END {print s}')
[ -n "$size" ] && [ "$size" -gt 0 ] && echo "$size $pid $(cat /proc/$pid/comm 2>/dev/null)"
done | sort -nr | head
这段脚本会输出“占用字节数、PID、进程名”三列,按大小倒序,排在最前面的基本就是你要找的对象。拿到 PID 之后,用 ps -fp <PID> 确认一下是什么服务,别误伤关键进程。需要说明的是,不同发行版的 /proc 字段格式可能有细微差异,具体以实际环境为准。
找到进程后,回收空间有两条路。第一条是重启该进程,进程退出时内核自动关闭所有描述符,空间立刻释放。这是最干净的做法,但会带来服务中断,适合可以重启、且重启代价可控的场景,比如 Nginx reload 不一定管用(老 worker 可能还握着旧句柄),需要真正 stop 再 start。
第二条是在不重启进程的前提下,把被删除文件的内容“清空”,从而让内核回收数据块,而进程持有的描述符仍然有效,继续往同一个 inode 写也不受影响。做法是通过 /proc/<PID>/fd/<FD> 这个路径,用重定向把它截断:
# 假设 PID 为 1234,被删除的日志描述符是 5
先确认目标,避免写错
ls -l /proc/1234/fd/5
把该文件截断为 0 字节,空间会被回收,进程仍可继续写
: > /proc/1234/fd/5
这里要特别提醒:重定向时用 : > 而不是 rm,rm 对 /proc 下的 fd 链接无效,还可能报错。另外,对数据库数据文件、正在被 mmap 的文件做截断风险很高,可能导致进程崩溃或数据损坏,这类文件请走重启方案,别图省事。操作前最好确认文件类型是普通文件而非设备、套接字。
还有一点值得提前做:给日志类文件配置 logrotate,并加上 copytruncate 选项,或者让应用支持重新打开日志文件(比如 Nginx 的 USR1 信号),从源头上避免“删了不还”的情况再次发生。临时救急之后,把根本的日志轮转策略补上,才算真正解决问题。
系统盘满但找不到大文件,先别急着扩容,按照“df 确认 → lsof +L1 定位 → 判断能否重启 → 截断或重启回收”的顺序走一遍,多数情况几分钟就能解决。日常运维里,建议把 lsof 这类排查工具提前装好,给磁盘使用率设一个 80% 的预警线,并把日志轮转策略纳入标准配置。下次再遇到 df 和 du 对不上,你就知道该往 /proc 里看一眼了。
| 📑 | 📅 |
|---|---|
| MySQL表空间与ibdata1膨胀处理:独立表空间、碎片整理与磁盘回收 | 2026-09-17 |
| SSH 连接频繁断开与卡顿排查:心跳、MTU 与 DNS 反解 | 2026-09-17 |
| Linux内存被缓存吃满:buff/cache该不该手动释放 | 2026-09-17 |
| 服务器时间不同步导致证书与定时任务异常:NTP/chrony 校时配置与排查 | 2026-09-16 |
| PostgreSQL 连接数与内存调优:max_connections 与 shared_buffers 怎么配 | 2026-09-16 |
| 服务器 swap 使用率飙升:正常换页还是内存真不够用 | 2026-09-17 |
| Docker容器时区不对?TZ变量与localtime挂载的正确用法 | 2026-09-17 |
| journald 日志占满 /var/log:持久化与容量限制配置 | 2026-09-18 |
| Linux网卡丢包与TCP重传排查:ip -s link、ss -ti与ethtool实战 | 2026-09-18 |
| Nginx 静态资源 404 与权限被拒排查:root、alias、try_files 的坑 | 2026-09-18 |