问题现象:删除文件后磁盘空间为何不释放?
你是否遇到过这种令人困惑的情况:
- 使用 df -h 查看磁盘,显示使用率高达 95%
- 但用 du -sh /var/log 统计目录大小,却发现只占用了 10GB
- ️ 明明用 rm -f catalina.out 删除了 50GB 的日志文件
- ❌ 磁盘空间却丝毫没有恢复!
这不是磁盘故障,也不是文件系统错误
而是有进程仍在”偷偷”占用已被删除的文件!
理解 Linux 文件删除机制
关键概念:文件删除 ≠ 空间立即释放
在 Linux 系统中,文件删除操作的实际流程是:
- 删除目录项:rm 命令只是移除文件在目录中的链接(unlink)
- 引用计数减一:每个文件都有链接计数,归零时空间才可回收
- 进程持有文件描述符:如果有进程仍打开该文件,空间不会被释放
通俗比喻:图书馆借书模型
将文件系统比作图书馆:
- 文件 = 书籍
- 目录项 = 图书目录卡片
- 进程 = 借阅者
- rm 命令 = 从目录中移除图书卡片
- 但:如果借阅者仍拿着书,书就不能上架供他人使用
只有等所有借阅者归还书籍(进程关闭文件),书架空间才能真正释放。
实战排查:三步定位问题进程
第一步:扫描被进程占用的已删除文件
#!/bin/bash
# find_deleted_files.sh - 扫描被进程占用的已删除文件
echo " 扫描被进程占用但已删除的文件..."
echo "=========================================="
# 使用 lsof 查找被标记为 deleted 的文件
lsof +L1 2>/dev/null | grep deleted | while read line; do
# 提取进程信息和文件大小
command=$(echo $line | awk '{print $1}')
pid=$(echo $line | awk '{print $2}')
user=$(echo $line | awk '{print $3}')
fd=$(echo $line | awk '{print $4}')
size=$(echo $line | awk '{print $7}')
filename=$(echo $line | awk '{for(i=9;i<=NF;i++) printf $i" "; print ""}')
# 转换大小为人类可读格式
if [ -n "$size" ] && [ "$size" -gt 0 ]; then
human_size=$(numfmt --to=iec $size 2>/dev/null || echo "${size}KB")
else
human_size="未知"
fi
echo "⚠️ 发现: PID=$pid, 进程=$command, 用户=$user, 大小=$human_size"
echo " 文件: ${filename}"
echo " FD: $fd"
echo "---"
done
# 如果没有找到,显示成功信息
if ! lsof +L1 2>/dev/null | grep -q deleted; then
echo "✅ 未发现被进程占用的已删除文件"
fi
关键输出解读:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 1234 root 1w REG 253,0 52428800 1234567 /app/logs/catalina.out (deleted)
nginx 5678 www 5w REG 253,0 1073741824 7654321 /var/log/nginx/access.log (deleted)
重点关注列:
- PID:占用文件的进程ID
- SIZE/OFF:文件占用空间大小(字节)
- (deleted):标记文件已被删除但未释放
第二步:深入分析问题进程
#!/bin/bash
# analyze_process.sh - 分析占用已删除文件的进程
if [ $# -eq 0 ]; then
echo "用法: $0 <进程PID>"
echo "示例: $0 1234"
exit 1
fi
PID=$1
echo " 分析进程 PID: $PID"
echo "========================"
# 检查进程是否存在
if [ ! -d "/proc/$PID" ]; then
echo "❌ 错误: 进程 $PID 不存在"
exit 1
fi
# 获取进程基本信息
echo " 进程基本信息:"
ps -fp $PID 2>/dev/null || echo " 无法获取进程信息"
# 获取进程打开的所有已删除文件
echo -e "
进程打开的已删除文件:"
lsof -p $PID 2>/dev/null | grep deleted | while read line; do
fd=$(echo $line | awk '{print $4}')
size=$(echo $line | awk '{print $7}')
filename=$(echo $line | awk '{for(i=9;i<=NF;i++) printf $i" "; print ""}')
if [ -n "$size" ] && [ "$size" -gt 0 ]; then
human_size=$(numfmt --to=iec $size 2>/dev/null || echo "${size}KB")
else
human_size="未知"
fi
echo " FD: $fd, 大小: $human_size, 文件: $filename"
done
# 获取进程的资源使用情况
echo -e "
进程资源使用:"
if [ -f "/proc/$PID/status" ]; then
echo " 内存(RSS): $(grep VmRSS /proc/$PID/status | awk '{print $2 $3}')"
echo " 虚拟内存: $(grep VmSize /proc/$PID/status | awk '{print $2 $3}')"
fi
# 获取进程启动时间和运行时间
if [ -d "/proc/$PID" ]; then
start_time=$(stat -c %Y /proc/$PID)
current_time=$(date +%s)
run_time=$(( (current_time - start_time) / 3600 ))
echo " 运行时间: ${run_time}小时"
fi
第三步:安全释放磁盘空间
根据进程类型选择合适的释放方案:
方案一:优雅重启/重载(推荐)
#!/bin/bash
# safe_space_release.sh - 安全释放磁盘空间
PID=$1
COMMAND=$2
case $COMMAND in
"nginx")
echo " 优雅重载 Nginx..."
nginx -t && nginx -s reload
;;
"tomcat"|"java")
echo " 重启 Tomcat/Java 应用..."
# 请根据实际路径修改
/opt/tomcat/bin/shutdown.sh
sleep 5
/opt/tomcat/bin/startup.sh
;;
"apache"|"httpd")
echo " 优雅重启 Apache..."
systemctl reload apache2
;;
*)
echo "⚠️ 未知服务类型,尝试优雅终止..."
kill -TERM $PID
sleep 3
if ps -p $PID > /dev/null; then
echo "❌ 优雅终止失败,进程仍在运行"
else
echo "✅ 进程已终止"
fi
;;
esac
# 验证空间释放
echo -e "
空间释放结果:"
df -h /var | grep -v Filesystem
方案二:强制终止(谨慎使用)
#!/bin/bash
# force_kill_process.sh - 强制终止进程(最后手段)
PID=$1
PROCESS_NAME=$(ps -p $PID -o comm= 2>/dev/null)
if [ -z "$PROCESS_NAME" ]; then
echo "❌ 进程 $PID 不存在"
exit 1
fi
echo "⚠️ 警告:即将强制终止进程"
echo " 进程: $PROCESS_NAME (PID: $PID)"
echo " 这可能导致数据丢失或服务中断!"
read -p "❓ 确认继续?(y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
echo " 终止进程 $PID ..."
kill -9 $PID
if [ $? -eq 0 ]; then
echo "✅ 进程已终止"
echo -e "
磁盘空间变化:"
df -h /var | grep -v Filesystem
else
echo "❌ 终止进程失败"
fi
else
echo "✅ 操作已撤销"
fi
预防措施:建立长效机制
1. 正确的日志轮转配置
错误示范:
# ❌ 危险操作:直接删除当前日志文件
rm -f /var/log/app.log
touch /var/log/app.log
正确配置(logrotate):
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
# 方案A:适用于无法接收信号的程序
copytruncate
# 方案B:适用于支持信号重载的程序
postrotate
# 对Nginx发送USR1信号重新打开日志
/bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || true
endscript
}
2. 应用层日志管理最佳实践
Java 应用:
<!-- logback.xml 配置示例 -->
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
3. 自动化监控告警系统
#!/bin/bash
# disk_space_monitor.sh - 磁盘空间监控告警
THRESHOLD=85
CURRENT_USAGE=$(df /var | awk 'NR==2 {print $5}' | sed 's/%//')
# 检查磁盘使用率
if [ $CURRENT_USAGE -gt $THRESHOLD ]; then
echo "⚠️ 磁盘使用率告警: ${CURRENT_USAGE}%"
# 检查是否有未释放的已删除文件
DELETED_FILES=$(lsof +L1 2>/dev/null | grep deleted | wc -l)
if [ $DELETED_FILES -gt 0 ]; then
echo " 发现 ${DELETED_FILES} 个被占用但已删除的文件"
# 生成详细报告
REPORT_FILE="/tmp/deleted_files_report_$(date +%Y%m%d_%H%M%S).txt"
lsof +L1 2>/dev/null | grep deleted > $REPORT_FILE
# 发送告警(可根据需要配置邮件、钉钉、企业微信等)
echo " 发现被进程占用的已删除文件,请及时处理" |
mail -s "磁盘空间告警 - $(hostname)" -a $REPORT_FILE admin@example.com
fi
fi
# 加入crontab,每5分钟检查一次
# */5 * * * * /opt/scripts/disk_space_monitor.sh
快速诊断流程图
磁盘空间异常 → df/du 结果不一致
↓
lsof +L1 扫描已删除但被占用的文件
↓
发现目标进程 → 分析进程类型和服务
↓
┌─────────────────┐
│ 选择处理方案 │
└─────────────────┘
↓
┌─────────────────┬─────────────────┐
│ 优雅重启/重载 │ 强制终止进程 │
│ (推荐) │ (最后手段) │
└─────────────────┴─────────────────┘
总结
核心要点回顾:
- 理解机制:Linux 中文件删除只是解除链接,空间释放需要进程关闭文件
- 排查工具:lsof +L1 是定位问题的关键命令
- 解决方案:优先使用优雅重启(reload/restart),慎用强制终止
- 预防为主:正确配置日志轮转,建立监控告警
记住这个简单公式:
问题排查 = lsof +L1(定位) + 优雅重启(解决) + 日志轮转(预防)
通过掌握这些技能,你就能在磁盘空间”消失”的迷雾中快速定位问题,确保系统稳定运行。提议将文中的脚本部署到生产环境,建立自动化的监控和处理机制。
© 版权声明
文章版权归作者所有,未经允许请勿转载。



这就是Linux垃圾的地方,按理说,比如Windows里,被占用的文件根本删都不能删的
收藏了,感谢分享