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。

相关推荐
名字还没想好☜1 天前
Python f-string 进阶:数字格式化、对齐填充、调试 = 号与嵌套表达式
开发语言·数据库·python·字符串格式化·f-string
IvanCodes1 天前
RAG 实战教程(一):RAG 工作原理与完整流程——分片、索引、召回、重排和生成
人工智能·后端·agent
ltl1 天前
内核追踪:ftrace、kprobe、uprobe、tracepoint 生产实战
linux
·薯条大王1 天前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
今天AI了吗1 天前
合规场景下的 AI 推理可解释性:Attention 可视化与推理路径追踪的工程实践
人工智能
周末程序猿1 天前
浅析大模型推理十二篇之KV Cache
人工智能
鱼樱前端1 天前
用 AI 做内容变收入
前端·人工智能·ai编程
Dawson Zhu1 天前
基于Palantir Foundry构建半导体制造AI友好型数据中台:从良率分析场景谈起
人工智能·语言模型·架构·制造·agi
fthux1 天前
装闭 RenoPit 源码解析(04):装修图纸和合同文件上传处理流程
人工智能·ai·开源·github·open source·renopit