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 进程上,因为它要准备参数。


三、验证与解决方案

✅ 验证方法:

  1. 检查 /data/ 下文件数量:

    复制代码
    ls -1 /data | wc -l
  2. 查看系统 ARG_MAX:

    复制代码
    getconf ARG_MAX
  3. 使用 strace 观察 bash 行为(谨慎在生产环境使用):

    复制代码
    strace -f -e trace=execve,openat bash -c 'du -sh /data/* > xx.txt'

✅ 优化方案(避免 bash 内存暴涨):

  1. 避免通配符展开,改用 find + xargs

    复制代码
    find /data -maxdepth 1 -mindepth 1 -print0 | xargs -0 du -sh > xx.txt
    • xargs 会自动分批处理参数,避免单次参数过长
    • -print0 和 -0 支持含空格/特殊字符的文件名
  2. 直接对父目录统计(如果需求允许):

    复制代码
    du -sh /data > xx.txt
    • 这会递归统计整个 /data,但不会展开子项为参数
  3. 使用脚本循环处理(适用于需逐个统计):

    复制代码
    for f in /data/*; do
        du -sh "$f"
    done > xx.txt
    • 每次只传一个参数给 du,bash 内存压力小
    • 但性能较低(多次调用 du)

四、结论

du -sh /data/* > xx.txt 导致 bash 进程内存显著增长,根本原因是 bash 在执行命令前需将 /data/* 展开为大量路径参数,这些参数存储在 bash 进程内存中,造成 VIRT/RES 上升。

这不是 du 的问题,而是 shell 通配符展开机制 在面对海量文件时的副作用。建议改用 find + xargs 等更稳健的方式处理大目录。

相关推荐
用户9718356334665 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪6 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言