ubuntu:内存假泄漏

1、发现问题起因

最近在ubuntu上开发了一个AI应用程序(用c++写的),通过循环拷机测试发现程序占用内存会不断往上涨(用htop监测虚拟内存VIRT和物理内存RES),虚拟内存可以从12G一直涨到30G,物理内存可以从起初的3G+涨到12G,怀疑内部存在严重的内存泄漏。

中间尝试了很多办法定位泄漏的地方,都莫名其妙的定位失败,包括利用valgrind工具分析、内部模块单个测试、注销某个单一模块测试等。

最终询问大模型才知这是内存假泄漏的现象。

2、什么是内存假泄漏?

虚拟内存或物理内存看起来在增长/波动,但实际上并没有发生代码级内存泄露。假泄漏的原因主要有以下几个因素:

2.1 多线程的 Arena 动态分配(ptmalloc 限制机制)-我的假泄漏就主要是这个原因

如果你的 C++ 程序使用了多线程(例如 std::thread、OpenMP 或第三方线程池),Glibc 的内存分配器(ptmalloc)会为每个线程或核心创建独立的内存分配区域,称为 Arena

  • 现象 :在 64 位系统下,ptmalloc 每次为一个新的 Arena 初始化的虚拟内存默认高达 64MB

  • 影响:线程创建的先后顺序、线程间的竞争激烈程度、以及内核调度的随机性,会导致每次运行中被激活的 Arena 数量不同。如果这次测试激活了 2 个 Arena,下次激活了 8 个,光是这部分就会导致虚拟内存暴增数百兆。

2.2 内存碎片化

程序频繁申请和释放不同大小的内存块,导致动态内存(堆)中充满了大量不连续的、微小的空闲孔洞

  • 现象:当程序后续需要申请一个较大块的内存时,虽然空闲孔洞的总和足够,但由于物理地址不连续,内存分配器(ptmalloc)无法使用这些孔洞,被迫再次向内核申请新的内存。这导致程序虽然释放了对象,但内存指标却一直在涨,形成"假泄露"。

2.3 内存留存/缓存池化(Memory Retaining / Caching)

为了避免频繁向内核发起系统调用(如 brkmmap)带来的性能损耗,内存分配器在程序执行 freedelete 后,并不会立刻把内存还给操作系统 ,而是保留在自己的私有缓冲池(如 Arena、Thread Cache)中,留给接下来的 new / malloc 复用。

现象:从操作系统和任务管理器的视角来看,该进程的物理内存(RSS)或虚拟内存(VIRT)到达峰值后就再也没有降下来,看起来就像是泄露了。

2.4 ASLR(地址空间布局随机化)

Linux 内核为了防止缓冲区溢出攻击,默认开启了 ASLR

  • 现象:每次启动程序时,内核会随机初始化进程的堆、栈、共享库映射区(mmaps)的起始地址。

  • 影响:为了在随机化后腾出足够连续的地址空间,或者因为对齐(Alignment)要求,内核在分配内存时会产生随机的地址空隙(Gaps)。这些空隙计入虚拟内存,导致每次运行的虚拟内存总额不同。

3、排查是否是假泄漏

第一步:临时关闭 ASLR 进行对比测试

复制代码
sudo sysctl -w kernel.randomize_va_space=0
  • 如果关闭后虚拟内存峰值完全稳定 :说明波动纯粹是由 Linux 安全机制(ASLR)引起的,无需优化代码,因为这不影响真实的物理内存。

  • 测试完后请恢复默认开启状态sudo sysctl -w kernel.randomize_va_space=2

第二步:限制多线程 Arena 数量

如果你的程序是多线程的,可以在启动程序前,通过环境变量限制 Glibc 允许创建的 Arena 最大数量:

复制代码
export MALLOC_ARENA_MAX=2
./your_cpp_program
  • 限制后如果虚拟内存峰值不再剧烈波动,说明是多线程内存分配器的 Arena 导致的。
  • 提示:将 MALLOC_ARENA_MAX 设低可以有效压减虚拟内存和物理内存碎片,但可能会轻微降低极高并发下的内存分配性能。

4、解决办法

直接想到的就是限制多线程 Arena 数量,但是这会带来多并发下的性能损失,建议使用jemalloc代替glibc的方案:

零代码修改:

复制代码
# 安装 jemalloc (Ubuntu)
sudo apt-get install libjemalloc-dev
# 在启动程序前通过 LD_PRELOAD 强制加载
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
./your_program

静态/动态编译链接:

复制代码
find_package(PkgConfig REQUIRED)
pkg_check_modules(JEMALLOC REQUIRED jemalloc)

add_executable(your_cpp_program main.cpp)
target_link_libraries(your_cpp_program PRIVATE ${JEMALLOC_LIBRARIES})

我的采用jemalloc之后虚拟内存峰值可以从31G减少到12G,物理内存可以从12G减少到2770M,耗时上基本上没有变化。

5、jemalloc vs glibc

5.1 核心架构与机制对比

5.2 性能与资源消耗对比(C++ 场景)

  1. 运行时耗时(Latency & QPS)
  • glibc: 在单线程下性能很好。但在多线程高并发下,因为锁竞争剧烈,耗时会发生无规律的"毛刺"(长尾延迟)。通过 MALLOC_ARENA_MAX=4 限制它,更是雪上加霜。
  • jemalloc 高并发下耗时曲线非常平滑,QPS 吞吐量通常能提升 15% ~ 30%
  1. 物理内存占用(RSS)
  • glibc: 长时间运行的 C++ 守护进程,物理内存通常会一路上扬("假泄漏"),业务低谷期内存也完全不下降。
  • jemalloc 内存呈"波浪形"。业务高峰期上涨,业务低谷期物理内存会真正回落
  1. 虚拟内存占用(VIRT)
  • glibc: 默认行为下,因为 64MB Arena 的机制,多线程程序的虚拟内存动辄高达几十 GB。
  • jemalloc 虽然也使用 mmap,但由于没有 64MB 固定块的限制,虚拟内存的膨胀速度远慢于 glibc。

相关推荐
赵成ccc2 小时前
【无标题】
linux·nginx
小船跨境2 小时前
2026 NLP数据采集指南:代理IP如何帮助提升大规模采集效率
大数据·网络·人工智能
前端技术官2 小时前
从搜索排名到 AI 回答? 介绍一下 AI 可见度工具 BuildSOM !
人工智能
ydyd202604212 小时前
十大设备全生命周期管理系统
人工智能
Ricky05532 小时前
BiFPN-YOLO:一种集成双向特征金字塔网络的一阶段目标检测方法(英国爱尔兰2025年联合研究)
人工智能·计算机视觉·目标跟踪
三品吉他手会点灯2 小时前
C语言学习笔记 - 36.数据类型 - 为什么需要输出控制符
c语言·开发语言·笔记·学习
刘一说2 小时前
Ubuntu 系统上安装 Docker
linux·ubuntu·docker
星马梦缘2 小时前
操作系统实验5 —— 进程互斥
linux·操作系统·进程互斥
断眉的派大星2 小时前
目标检测中的区域建议(Region Proposal):高效候选框初筛
人工智能·目标检测·目标跟踪