Linux 磁盘空间不释放问题排查(lsof 定位已删除仍被占用文件)
问题现象
生产环境中经常遇到这类场景:
- •
df -h 看到某分区磁盘使用率已经很高,甚至 100%。 - • 用
du -sh 目录 逐层统计,却发现目录实际占用空间远小于 df 显示的使用量。 - • 之前用
rm -f 或日志清理脚本删除过大文件,但磁盘空间没有释放。
这种"删了文件空间却没回来"的情况,最常见原因是:文件虽然被删除,但仍有进程持有该文件的文件描述符(fd),内核不会真正回收其占用的磁盘块,直到所有引用都释放。
根因
Linux 文件系统中,文件通过 inode 管理磁盘块。rm 命令实际上是减少文件的硬链接计数(link count)。当硬链接计数降到 0,且没有任何进程打开该文件(文件描述符引用数为 0)时,内核才会释放 inode 及其占用的磁盘块。
如果进程还打开着这个文件(比如 Tomcat 的 catalina.out、Nginx 的 access log、业务应用的日志句柄),即使文件在目录树中已经不可见,磁盘块仍然被占用。
排查步骤
1. 确认磁盘空间确实紧张
df -h
关注点:Use% 接近或达到 100% 的分区。
2. 定位是哪些大文件/目录在占用空间
# 查看当前目录下各文件/目录占用du -sh * 2>/dev/null | sort -rh | head -20# 或从根目录开始排查某个挂载点du -sh /path/* 2>/dev/null | sort -rh | head -20
如果 du 统计出来的大小明显小于 df 显示的使用量,就要怀疑存在"已删除但仍被占用"的文件。
3. 用 lsof 找出已删除但仍被占用的文件
3.1 全局扫描所有进程的 deleted 文件
lsof +L1
+L1 表示只列出 link count 小于 1 的文件,也就是已被删除但仍被进程持有的文件。
3.2 只查看指定挂载点下的 deleted 文件
lsof +L1 /path
例如只查 /work_app 分区:
lsof +L1 /work_app
3.3 查看指定进程打开的 deleted 文件
如果已经知道可疑进程,比如 Tomcat:
# 先找到进程 PIDps -ef | grep java# 再查看该进程打开的 deleted 文件lsof -p <PID> | grep delete
示例输出(Tomcat 的 catalina.out 被删除后仍被占用):
java 257281 tomcat 1w REG 253,0 80839073792 202612073 /work_app/apache-tomcat-7.0.109_6088/logs/catalina.out (deleted)java 257281 tomcat 2w REG 253,0 80839073792 202612073 /work_app/apache-tomcat-7.0.109_6088/logs/catalina.out (deleted)
字段说明:
- •
FD:文件描述符,1w 表示标准输出(stdout)写模式,2w 表示标准错误(stderr)写模式。 - •
SIZE/OFF:文件大小,这里就是占用磁盘空间的字节数。 - •
NAME:文件路径,末尾 (deleted) 表示文件已被删除。
从示例可以看到,catalina.out 被删除后仍然被 Tomcat 进程以 1w 和 2w 两个 fd 持有,大小约 80 GB。
4. 确认空间占用与进程的对应关系
如果 lsof +L1 列出的 deleted 文件很多,可以按大小排序快速定位大头:
lsof +L1 | awk '{print $7, $1, $2, $NF}' | sort -rn | head -20
注意:lsof 输出列数不固定,严格排序建议用 lsof -F 指定输出格式,或结合 du -x 排查。
解决方案
释放空间的关键是:让持有 deleted 文件的进程关闭文件句柄。常见做法有三种,按风险从低到高排列:
方案 1:清空/截断仍在写入的文件(推荐,无需重启服务)
如果文件没有被删除,只是日志持续膨胀,可以直接清空文件内容:
# 先确认是哪个 fd 在占用空间lsof -p <PID> | grep 日志文件名# 通过 /proc/<PID>/fd/<fd_num> 截断文件# : > /proc/<PID>/fd/<fd_num>echo "" > /proc/<PID>/fd/<fd_num>
例如:
: > /proc/257281/fd/1
这种写法不会删除 inode,只是把文件内容清空,进程可以继续往同一个 fd 写入,不需要重启服务。
不要直接 rm -f 后再用 > 重定向,因为进程仍然持有旧的 deleted inode,新建的文件是另一个 inode,旧空间依然占着。
更安全的做法是使用 logrotate 的 copytruncate 机制:先复制原日志,再截断原文件,无需重启进程。
方案 2:重启占用进程
如果文件已经被删除且进程不需要保留当前日志流,可以重启进程释放 fd:
# 找到服务启动脚本或 systemd 服务kill -9 <PID># 然后重新启动服务
以 Tomcat 为例:
# 切换到应用用户(避免 root 启动导致权限问题)su - tomcatcd /work_app/apache-tomcat-7.0.109_6088/bin/# 先停止(或 kill)kill -9 257281# 再启动sh startup.sh
注意:-9 是强制终止,可能导致正在处理的请求中断。对于在线业务,建议先用正常停止命令(如 shutdown.sh 或 systemctl stop),等待进程优雅退出;只有正常退出失败时才使用 kill -9。
预防措施
- • 使用
logrotate 配置按天或按大小切割日志,启用 copytruncate 或配合应用重启策略。 - • Java 应用推荐配置
logging.properties 或 logback/log4j2 的滚动策略,避免单文件无限增长。
- • 清理前先
lsof 确认是否有进程持有该文件。 - • 对于运行中的服务,优先使用截断或轮转方式,而不是直接删除。
- • 对磁盘使用率设置告警阈值(如 80%、90%)。
- • 对关键目录的
du 与 df 差异设置监控,及时发现 deleted 文件泄漏。
- • 避免把 stdout/stderr 直接重定向到固定大文件;使用日志框架自带的滚动策略。
- • 容器场景下,注意 Docker daemon 日志(
/var/lib/docker/containers/...)也可能因容器未重启而占用已删除日志空间。
常用命令速查
| |
|---|
| df -h |
| du -sh /path |
| lsof +L1 |
| lsof +L1 /path |
| lsof -p <PID> | grep delete |
| : > /proc/<PID>/fd/<fd_num> |
| lsof +L1 | awk '{print $7, $1, $2, $NF}' | sort -rn | head |
总结
"文件删除空间不释放"本质上是进程还持有被删文件的描述符。排查三板斧:
- 2.
du -sh 与 df 对比,怀疑 deleted 文件泄漏。 - 3.
lsof +L1 或 lsof -p <PID> | grep delete 定位占用进程和 fd。
处理时优先选择截断文件或优雅重启进程;强制 kill -9 只作为兜底手段。配合日志轮转和监控告警,可以从根本上避免这类问题反复出现。