Linux 查询目录下文件大小引发的内存溢出问题
在 Linux 系统中执行命令:
du -sh /data/* > xx.txt
并观察到 top 中有一个 bash 进程的 VIRT(虚拟内存)
和 RES(常驻内存)
显著增长,这种现象看似反常,因为通常 du 命令本身才是主要消耗资源的进程,而不是 bash。下面从命令执行逻辑和内存增长原因两方面进行分析。
一、命令执行逻辑分析
1. Shell 展开通配符 /data/*
当你在终端输入:
du -sh /data/* > xx.txt
bash 首先会进行通配符(glob)展开,即把 /data/* 替换为 /data/ 目录下所有文件和子目录的实际路径列表。例如:
du -sh /data/file1 /data/file2 /data/dir1 /data/dir2 ... > xx.txt
这个展开过程完全由 bash shell 本身完成,在 fork/exec du
之前。
2. 参数列表长度可能非常大
如果 /data/ 目录下有成千上万个文件或子目录,那么展开后的命令行参数数量会非常庞大。例如:
- 10 万个文件 → 10 万个参数传递给 du
- 每个路径平均 50 字节 → 参数总长度约 5 MB
而 Linux 对单个进程的命令行参数和环境变量总长度有限制(由 ARG_MAX
定义,通常为 2 MB 或更大,可通过 getconf ARG_MAX
查看)。
如果参数总长度超过 ARG_MAX
,bash 会直接报错:Argument list too long
。
但在未达到上限的情况下,bash 仍需在内存中构建这个庞大的参数列表,用于后续 execve()
调用。
3. bash 构建参数列表时占用内存
在执行命令前,bash 需要:
- 读取 /data/ 目录内容(通过
readdir
等系统调用) - 构建一个包含所有匹配路径的字符串数组(
argv[]
) - 将这个数组保留在内存中,直到
execve
成功或失败
这个过程会导致 bash 进程自身的内存使用(RES 和 VIRT)显著上升,尤其是在目录项极多时。
4. 重定向 > xx.txt 由 bash 处理
输出重定向也是由 bash 在 fork
子进程前设置好文件描述符,但这部分开销很小,不是内存增长主因。
二、内存增长原因总结
原因 | 说明 |
---|---|
通配符展开在 bash 内存中完成 | bash 必须将 /data/* 展开为完整路径列表,存储在内存中 |
大量目录项导致 argv 数组巨大 | 每个文件/目录名都成为 du 的一个参数,bash 需分配内存存储这些字符串 |
RES/VIRT 增长反映在 bash 进程上 | 因为展开发生在 fork/exec du 之前,内存分配属于 bash 自身 |
du 尚未启动或刚启动 | 在 top 中可能看到 bash 内存飙升,而 du 还没完全接管工作 |
⚠️ 注意:即使 du 后续运行并消耗内存,初始的内存 spike 通常出现在 bash 进程上,因为它要准备参数。
三、验证与解决方案
✅ 验证方法:
-
检查 /data/ 下文件数量:
ls -1 /data | wc -l
-
查看系统 ARG_MAX:
getconf ARG_MAX
-
使用 strace 观察 bash 行为(谨慎在生产环境使用):
strace -f -e trace=execve,openat bash -c 'du -sh /data/* > xx.txt'
✅ 优化方案(避免 bash 内存暴涨):
-
避免通配符展开,改用
find + xargs
:find /data -maxdepth 1 -mindepth 1 -print0 | xargs -0 du -sh > xx.txt
- xargs 会自动分批处理参数,避免单次参数过长
- -print0 和 -0 支持含空格/特殊字符的文件名
-
直接对父目录统计(如果需求允许):
du -sh /data > xx.txt
- 这会递归统计整个 /data,但不会展开子项为参数
-
使用脚本循环处理(适用于需逐个统计):
for f in /data/*; dodu -sh "$f" done > xx.txt
- 每次只传一个参数给 du,bash 内存压力小
- 但性能较低(多次调用 du)
四、结论
du -sh /data/* > xx.txt
导致 bash 进程内存显著增长,根本原因是 bash 在执行命令前需将 /data/* 展开为大量路径参数,这些参数存储在 bash 进程内存中,造成 VIRT/RES 上升。
这不是 du 的问题,而是 shell 通配符展开机制 在面对海量文件时的副作用。建议改用 find + xargs
等更稳健的方式处理大目录。