系统盘满了却找不到大文件:被删除但仍被进程占用的句柄排查与恢复空间

    发布时间: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 和 /proc 把占用的进程揪出来

    定位被删除但仍被占用的文件,最直接的工具是 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