Linux 磁盘空间不释放问题排查(lsof 定位已删除仍被占用文件)

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:

# 先找到进程 PID
ps -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)

字段说明:

  • PID:进程 ID(如 257281)。
  • USER:进程用户(如 tomcat)。
  • FD:文件描述符,1w 表示标准输出(stdout)写模式,2w 表示标准错误(stderr)写模式。
  • TYPE:文件类型,REG 表示普通文件。
  • DEVICE:设备号。
  • SIZE/OFF:文件大小,这里就是占用磁盘空间的字节数。
  • NODE:inode 号。
  • NAME:文件路径,末尾 (deleted) 表示文件已被删除。

从示例可以看到,catalina.out 被删除后仍然被 Tomcat 进程以 1w2w 两个 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,旧空间依然占着。

更安全的做法是使用 logrotatecopytruncate 机制:先复制原日志,再截断原文件,无需重启进程。

方案 2:重启占用进程

如果文件已经被删除且进程不需要保留当前日志流,可以重启进程释放 fd:

# 找到服务启动脚本或 systemd 服务
kill -9 <PID>
# 然后重新启动服务

以 Tomcat 为例:

# 切换到应用用户(避免 root 启动导致权限问题)
su - tomcat
cd /work_app/apache-tomcat-7.0.109_6088/bin/

# 先停止(或 kill)
kill -9 257281

# 再启动
sh startup.sh

注意:-9 是强制终止,可能导致正在处理的请求中断。对于在线业务,建议先用正常停止命令(如 shutdown.shsystemctl stop),等待进程优雅退出;只有正常退出失败时才使用 kill -9

预防措施

  1. 日志按大小/日期轮转
    • 使用 logrotate 配置按天或按大小切割日志,启用 copytruncate 或配合应用重启策略。
    • Java 应用推荐配置 logging.propertieslogback/log4j2 的滚动策略,避免单文件无限增长。
  2. 禁止直接 rm -f 删除正在写入的日志
    • 清理前先 lsof 确认是否有进程持有该文件。
    • 对于运行中的服务,优先使用截断或轮转方式,而不是直接删除。
  3. 监控与告警
    • 对磁盘使用率设置告警阈值(如 80%、90%)。
    • 对关键目录的 dudf 差异设置监控,及时发现 deleted 文件泄漏。
  4. 应用日志输出规范
    • 避免把 stdout/stderr 直接重定向到固定大文件;使用日志框架自带的滚动策略。
    • 容器场景下,注意 Docker daemon 日志(/var/lib/docker/containers/...)也可能因容器未重启而占用已删除日志空间。

常用命令速查

场景 命令
查看磁盘空间 df -h
查看目录大小 du -sh /path
查看所有 deleted 文件 lsof +L1
查看指定目录下的 deleted 文件 lsof +L1 /path
查看指定进程的 deleted 文件 lsof -p <PID> \| grep delete
截断进程 fd 对应的文件 : > /proc/<PID>/fd/<fd_num>
按大小排序 deleted 文件 lsof +L1 \| awk '{print $7, $1, $2, $NF}' \| sort -rn \| head

总结

“文件删除空间不释放”本质上是进程还持有被删文件的描述符。排查三板斧:

  1. df -h 确认空间紧张。
  2. du -shdf 对比,怀疑 deleted 文件泄漏。
  3. lsof +L1lsof -p <PID> | grep delete 定位占用进程和 fd。

处理时优先选择截断文件优雅重启进程;强制 kill -9 只作为兜底手段。配合日志轮转和监控告警,可以从根本上避免这类问题反复出现。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇