ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Linux文件删除后磁盘空间不释放?三步定位进程占用与处理技巧

2026/10/5 10:40:22 拓冰建站 浏览量
Linux文件删除后磁盘空间不释放?三步定位进程占用与处理技巧 先讲一个我处理过很多次的现场你在一台 Linux 服务器上发现/data分区快满了于是把里面一个 12GB 的临时目录rm -rf删掉回头再看df -h使用率还是 94%一点没降。这时候新手一般会怀疑是命令没执行成功再删一遍有经验的运维则会先停下来因为大家心里都清楚Linux 下文件删除后空间不释放是个老熟人几乎每周都要见几次。这篇文章我打算把文件删了但磁盘空间不释放这件事从头到尾拆开讲从最经典的进程占用文件句柄讲起再延伸到硬链接、挂载点、inode 耗尽、LVM 快照这些容易被忽略的“障眼法”。内容会包含可以直接抄的排查命令、原理说明和几个我踩过才知道的处理技巧。无论你是刚接触 Linux 的运维新手还是经常处理线上故障的工程师这套思路都能让你在十分钟内定位到问题根因。1. 现象df 显示空间没释放文件明明已经删了1.1 最先要复现的现场处理这种问题第一步永远是先复现现象把命令跑一遍确认到底什么情况。我通常按这个顺序来df -h /data du -sh /data/tmp rm -rf /data/tmp df -h /data前两条命令确认分区使用率和目标目录大小第三条删除第四条看结果。如果删除后df的 Avail 和 Use% 都没变化那问题基本就实锤了文件对应的磁盘空间并没有真正释放。这里有个细节值得注意df看的是文件系统整体du看的是目录树里还能看到的文件。当你删掉一个文件后如果这个文件被某个进程“拿住”了目录树里已经看不到它du自然统计不到可磁盘块还实打实地被占用df就会告诉你“空间没少”。所以用du -sh /data排查时你会惊讶地发现目录里所有文件加起来根本没那么大但df显示分区几乎满了。这种现象基本可以锁定是有已删除文件仍被进程占用。1.2 原理inode 引用归零才是真正释放Linux 文件系统把“文件名”和“数据块”拆成了两部分目录项dentry和 inode。你在命令行rm一个文件实际是把这个文件的目录项从父目录里移除同时把该 inode 的硬链接计数减一。只有 inode 的链接计数降到 0内核才会认为这个文件彻底没人用了回收对应的数据块。但“没人用”还有一个条件不能有进程持有它的文件描述符。进程打开文件时内核会让这个 inode 的引用计数i_count加一进程不关闭文件描述符引用计数就一直大于 0。此时哪怕目录项删光了、硬链接计数已经是 0文件占用的数据块依然被内核扣着谁也拿不走。我习惯用生活里的场景来类比文件占用的磁盘块就像办公室里的工位目录项相当于贴在工位上的员工名牌。你把名牌撕了rm但这位员工还坐在工位上干活行政内核就没法把工位重新分配给新人。只有员工离开工位进程关闭文件描述符或者被请出办公室进程结束工位才能真正空出来。1.3 哪些场景最容易触发“假删除”这类问题之所以常见是因为符合“文件删除但进程持续写入”的动作实在太多了。我在生产环境里遇到过的典型场景包括日志文件Nginx、Tomcat、Java 应用、syslog 等进程一直打开着日志文件运维删除旧日志时忘了先处理对应服务。临时文件FTP 上传中的临时文件、Python 脚本生成的临时文件、/tmp下的暂存数据进程还握着文件描述符没释放。数据库文件MySQL、PostgreSQL 在运行的 redo log、undo log或某些被手动清理的大文件。Docker 容器日志容器 stdout 日志通常以 json-file 形式存储如果容器一直运行即使外部删了日志文件空间也照样不释放。大文件导出/导入过程比如用mysqldump导出、用tar打包任务没结束之前删除文件进程一旦还持有文件描述符空间就会一直被占着。遇到“空间不释放”先别急着重启机器更不要盲目去删更多文件。按照下面这套流程走一般都能精准定位。2. 定位占着“坑”的进程三步速查2.1 用 lsof L1 一招锁定定位“已删除但仍被进程打开”的文件最干净利落的工具是lsof。加L1参数的意思是只列出 link count 小于 1 的文件也就是当前已经被删除、但仍被某个进程占用的文件。sudo lsof L1执行结果大概长这样COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 5420 root txt REG 253,0 317148 623 /usr/sbin/nginx (deleted) java 9999 app 23w REG 253,2 1024M 5689 /var/log/app/access.log (deleted)重点看最后一列NAME只要带着(deleted)标记就说明该文件已经被删但还被进程占用着。SIZE/OFF会告诉我们这个占用的文件多大PID和FD则是后续操作的关键信息。如果输出比较多可以过滤一下只关注根因所在分区sudo lsof L1 | grep deleted df -h /data sudo lsof L1 | grep /data如果进程比较多也可以用lsof | grep filename直接查指定文件。不过要注意文件名已经被删掉了普通路径查找经常查不到这时候L1才是真正可靠的入口。2.2 没有 lsof 时不慌/proc 文件系统也能查有的最小化安装环境里没有lsof又不能随便装包。这时可以借助 Linux 的/proc文件系统实现同样的效果。每个进程运行时的文件描述符都暴露在/proc/pid/fd/下已删除文件对应的软链接目标会带上(deleted)字样sudo ls -l /proc/*/fd 2/dev/null | grep deleted输出类似lrwx------ 1 root root 64 15:32 3 - /var/log/nginx/error.log (deleted) lrwx------ 1 root root 64 15:32 3 - /tmp/tmp123.zip (deleted)每行开头没有直接显示 PID要看完整路径或配合ls -l /proc/pid/fd逐 PID 确认。也可以写个简单循环for pid in /proc/[0-9]*; do p$(basename $pid) ls -l /proc/$p/fd 2/dev/null | grep -q (deleted) echo --- PID $p --- ls -l /proc/$p/fd 2/dev/null | grep (deleted) done这种方式不需要额外安装任何工具在任何 Linux 上都能用。/proc里看到的信息和lsof正本同源区别只是展示形式更原始但足够完成定位。2.3 找到进程后先别急着杀先掂量再动手lsof列出哪个进程占用不代表就要立刻把它干掉。我看到很多人一找到 PID 就kill -9在非必要场景这可能引发更严重的故障。第一个需要确认的问题是到底是哪个进程在写这个文件如果是 Nginx、Apache、syslog 这类日志服务一般可以通过优雅重开日志或重启服务解决。如果是数据库、业务应用贸然 kill 可能导致数据不一致或长事务中断这时候一定要评估能否重启或者选择后续讲到的“清空句柄”方式。第二个问题是这个占用文件是不是真的占了大量空间可以用下面命令确认单个文件描述符对应的文件大小sudo ls -l /proc/pid/fd/fd号比如ls -l /proc/9999/fd/23看到大小几 GB那基本就是它了。如果只有几十 KB那它不是空间不释放的元凶可以继续找别的(deleted)文件。3. 让空间释放的三种落地操作3.1 重启服务最稳妥但需要窗口最直观的解法是让持有文件描述符的进程退出。进程结束后内核会把所有 fd 关闭对应 inode 的引用计数归零磁盘空间随即释放。如果是 Nginx可以优先尝试重开日志而不是重启整个服务。Nginx 会监听USR1信号收到后重开所有日志文件kill -USR1 $(cat /var/run/nginx.pid)这个操作不会中断现有连接对业务影响极小。如果是自定义服务很多 daemon 也支持HUP信号重载或重开日志建议先看进程文档再动手。如果服务不支持优雅信号那只能重启服务。运维习惯里有一条重启前先确认业务低峰期再看有没有其他节点可以扛流量。像 Java 应用如果只是日志句柄占用没必要重启整个 JVM可以结合后面的“清空句柄”方式处理。数据库这类服务更不要轻易 restart要对事务和连接有通盘考虑。3.2 清空已删除文件句柄进程不中断也能释放有时候进程不能重启比如数据库正承担核心业务或者重启代价很大。但文件已经删了我们仍然可以“主动截断”那个已删除文件让内核立刻释放数据块。具体思路是找到进程持有的已删除文件的 fd 路径用重定向或truncate把它截断为 0。对内核来说这个文件虽然不可见但它是真实存在于 inode 层级的可以通过/proc/pid/fd/fd号访问到。首先定位 fd 号sudo ls -l /proc/pid/fd/ | grep deleted假设输出显示23 - /var/log/app/access.log (deleted)那么执行sudo : /proc/pid/fd/23或者更明确一点sudo truncate -s 0 /proc/pid/fd/23执行后再看df -h空间马上就能降下来。原理很简单截断操作把文件长度置为 0内核会把不再需要的数据块标记为空闲。进程依然持有这个 fd后续如果要继续写入文件会从截断后的位置重新增长但当前被占用的空间已经释放了。这里有个坑必须提醒对数据库日志类文件的句柄做截断需要非常谨慎。数据库进程往往对日志文件有精确的偏移量管理直接截断可能让数据库检测到文件大小异常甚至触发恢复流程。我的做法是只有对业务影响可控的应用日志、临时文件才敢这么操作数据库日志宁可走维护窗口重启或用数据库自己的日志轮换机制。3.3 日志轮转与后续预防别让同一问题反复出现定位和释放是一次性救火但真正省心的是配好日志轮转让“文件被删除但空间不释放”这个问题从源头消失。我强烈推荐logrotate。以应用日志为例可以在/etc/logrotate.d/下建一个配置/var/log/app/*.log { daily rotate 30 compress missingok notifempty copytruncate }重点说下copytruncate这个选项。它先复制原日志文件内容到轮转文件然后立刻把原文件截断为 0。因为进程始终持有的是同一个 fd对它做截断操作不会导致进程重启也不会造成文件被释放后进程继续写入旧块的尴尬局面。最大的优点是干净、自动、不用管进程美中不足是“复制”和“截断”之间有极短时间内写入的日志会丢但对绝大多数应用损失几行日志完全能接受。如果你的服务支持信号重开日志可以用更优雅的配置。比如 Nginx 场景下很多人会去掉copytruncate改用/var/log/nginx/*.log { daily rotate 30 compress missingok notifempty postrotate kill -USR1 $(cat /var/run/nginx.pid) endscript }这样日志轮转后立刻让 Nginx 重新打开日志文件进程 fd 指向新文件旧文件被正常归档且不再被占用。Java 应用如果在用 Log4j、Logback应该在应用内部配置 RollingFileAppender 的滚动时间和归档策略而不是靠运维手工rm。MySQL 的 binlog 和 redo log 也要用数据库自身的参数维护比如expire_logs_days或binlog_expire_logs_seconds。只要把轮转变成制度化操作这类问题会下降八成。4. 不释放还有哪些“障眼法”少踩这些隐蔽坑4.1 硬链接和多路径引用有一种情况不关进程的事而是你删除的路径并不是该数据的唯一入口。同一个 inode 可以被多个目录项引用也就是硬链接。删除其中一个路径后其他硬链接路径依然存在inode 的链接计数没有归零数据块自然会继续占用空间。排查方式是用stat看文件 inode 号再全盘找相同 inodestat /data/test.txt确认 inode 号后用find /data -xdev -inum inode号 -exec ls -l {} \;如果找出两个或多个路径那就说明有硬链接残留。处理方式是把多余路径删干净保留一个即可。软链接不会造成这种问题因为它只是指向路径的快捷方式不影响 inode 引用计数。4.2 挂载点嵌套与删除目录另一个经典误判是你删除了一个目录但目录下面其实挂载了另一个文件系统。比如/data/tmp是一个独立分区执行rm -rf /data/tmp只会删除挂载点这个空目录项挂载点下面的文件属于另一个文件系统根本不会被删掉。df -h /data发现没变化但其实很合理。排查命令mount | grep /data findmnt /data/tmp如果确认/data/tmp是挂载点先umount /data/tmp再整理它底下的内容。否则你删的只是“门牌”里面的数据依旧躺在另一边。还有一种挂载相关的坑是umount -l。强制延迟卸载后原挂载点可能还在被某些进程使用文件系统没有真正释放df显示依然占用着空间。此时可以回到第 2 章的排查方法用lsof L1 | grep 原挂载目录找到持有句柄的进程处理掉后再真正卸载。4.3 df 和 du 对不上快照、inode、lostfound如果已经排查过进程占用、硬链接、挂载点df和du还是对不上就得怀疑文件系统层面的因素了。首先是文件系统预留块。ext3/ext4 默认会预留 5% 的空间给 root 用户df会把这部分算成已用空间。如果你的分区很大5% 就是不小的容量。用下面的命令查看tune2fs -l /dev/设备 | grep -i reservedReserved block count和Reserved block percentage会直接显示出来。如果业务确实需要极限利用空间可以调低预留比例但不建议设为 0否则文件系统碎片化和异常恢复能力都会变差。其次是 inode 耗尽。df -h显示还有空间但新建文件报No space left on device这时候要看df -i /data如果IUse%接近 100%说明是 inode 满了不是块空间满了。这种场景通常由海量小文件导致。删除大量小文件时要小心比如用find /data/cache -type f -delete比rm -rf更可控也避免路径过长误伤其他目录。然后是 LVM 快照。如果系统使用 LVM删除原卷大文件后空间没减少先看有没有快照lvs lvdisplay快照会保留被修改前的块数据如果快照空间太小占满后源卷的写操作会变得异常给人“空间不释放”的错觉。排查确认后要么扩展快照空间要么在确认快照不需要时直接删除快照。还有一类低频但真实存在的场景fsck后大量丢失链文件被放到lostfound目录。如果系统发生过异常断电建议看一眼du -sh /lostfound如果目录很大占用空间的就是这些“捡回来的碎片”。确认无用后可以清理但要先仔细查看是否有值得恢复的数据。4.4 从现象到处置的一页排查清单为了让你遇到问题时不用来回翻文章我把完整排查路径整理成表现象特征可能原因首选排查命令处理思路删除文件后 df 不变du 已变小进程持有已删除文件句柄lsof L1/ ls -l /proc/*/fdgrep deleted删除目录后空间没变目录实际是挂载点删的是挂载点空壳mount/findmntumount 后清理真实数据删了一个路径另一个路径还在硬链接find /data -xdev -inum inode删除多余硬链接df 显示空间满但目录内文件很小文件系统预留块 / 快照 / 挂载影响tune2fs -l、lvs、mount按对应原因调整空间还有但无法新建文件inode 耗尽df -i清理小文件fsck 后空间异常lostfound 碎片文件du -sh /lostfound检查后清理这张表就是我的日常速查卡遇到问题按表走基本不会跑偏。5. 再聊聊我自己的实操习惯处理“Linux 文件删除后空间不释放”的问题我的习惯是先不要动生产环境的任何进程先收集信息。df -h、df -i、du -xsh /*、lsof L1这几条命令是固定的开场组合跑完基本能筛选出 80% 的原因。个人经验里进程占用已删除文件是最常见的十个案例里至少有八个都是它。所以接到这类反馈我总会先查lsof L1而不是先去翻业务日志。确认有进程占着文件又暂时不能重启时我会用: /proc/pid/fd/fd的方式应急释放空间同时立刻评估这个文件是否适合长期保持“进程持有但长度为零”的状态如果不适合就着手配置日志轮转或计划重启。还有一个容易忽略的细节如果文件系统是 NFS删除行为会和本地文件系统不太一样。NFS 客户端删掉文件后可能因为语义缓存和 close-to-open 一致性服务端释放空间会有延迟。这时候不要反复在客户端重试等一会儿再看或者到 NFS 服务端确认实际释放情况。最后如果你在线上遇到这类问题我的建议是永远优先保留业务连续性先定位再选择最轻量的释放手段。能重开日志就不重启服务能截断句柄就不 kill 进程能配置轮转就不手工删除。这套方法论不仅对“空间不释放”有效对很多 Linux 运维故障都是通用的底层思路。