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 等更稳健的方式处理大目录。

相关推荐
聆风吟º19 小时前
CANN开源项目深度实践:基于amct-toolkit实现自动化模型量化与精度保障策略
运维·开源·自动化·cann
Coder个人博客20 小时前
Linux6.19-ARM64 mm mmu子模块深入分析
大数据·linux·车载系统·系统架构·系统安全·鸿蒙系统
较劲男子汉1 天前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
Doro再努力1 天前
Vim 快速上手实操手册:从入门到生产环境实战
linux·编辑器·vim
wypywyp1 天前
8. ubuntu 虚拟机 linux 服务器 TCP/IP 概念辨析
linux·服务器·ubuntu
风流倜傥唐伯虎1 天前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Doro再努力1 天前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
senijusene1 天前
Linux软件编程:IO编程,标准IO(1)
linux·运维·服务器
忧郁的橙子.1 天前
02-本地部署Ollama、Python
linux·运维·服务器
醇氧1 天前
【linux】查看发行版信息
linux·运维·服务器