程序莫名内存”泄露“,可能是glibc malloc Arena引发的

在ubuntu20.04下由arena引发的内存”泄露“

  1. 注:标题中的”泄露“不是真正的内存未被用户释放的泄露;而是在程序用用户正确的释放堆中的内存,长期运行时,内存在不停的增长丝毫没有降低的趋势。

近期,在linux环境中开发硬件固件时出现程序运行时内存在不停的增长,而上一个版本被没有此问题。使用valgrind测试发现又没有内存泄露问题!-……-难搞啊。

大胆猜测!

是不是内存释放导致的,使用jemalloc替换系统自带的malloc测试发现,内存完全没有泄露的迹象。

jemalloc使用方式
wget https://github.com/jemalloc/jemalloc/archive/refs/tags/5.3.0.tar.gz
tar zxvf 5.3.0.tar.gz
cd jemalloc-5.3.0/
./autogen.sh 

# 这里启动堆剖析功能
./configure --enable-prof 
make -j 
make install
使用jemalloc需要两个步骤

1.在debug时,需要进行采样配置MALLOC_CONF

# ""配置选项皆可以在https://jemalloc.net/jemalloc.3.html#opt.prof_leak找到
export MALLOC_CONF="prof_leak:true,lg_prof_sample:20,prof_final:true"
  • prof_leak,用来分析内存泄露。
  • lg_prof_sample:20,采样内存的平均间隔(以 2 为底的对数),以分配活动字节数为单位。这里设置为 20,即每申请 2^20 字节的内存就会采样一次。默认情况下是 2^19(512KB)。
  • prof_final: true,在程序退出的时候生成一次 dump。

2.编辑测试程序并运行程序

// testleak.cc
#include <stdio.h>
#include <stdlib.h>
void do_something(size_t i)
{
  // Leak some memory.
 char* buffer = (char*)malloc(i * 1024);
}
void do_something_else(size_t i)
{
  // Leak some memory.
  char* buffer = (char*)malloc(i * 4096);
  char* buf1 = new char[i*4096];
}

int main(int argc, char **argv)
{
  size_t i, sz;
  for (i = 0; i < 80; i++)
  {
    do_something(i);
  }
  for (i = 0; i < 40; i++)
  {
    do_something_else(i);
  }
  return (0);
}
# 编译
g++ testleak.cc -o testleak -ljemalloc
# 使用LD_PRELOAD可以使执行文件连接到jemalloc上
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" ./testleak

执行完成后会生成该程序对应的jeprof.xxx.heap文件

使用jeprof分析或生成pdf文件进行查看

$ jeprof testleak jeprof.1384301.0.f.heap
Using local file testleak.
Using local file jeprof.1384301.0.f.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 2.1 MB
     2.1 100.0% 100.0%      2.1 100.0% prof_backtrace_impl
     0.0   0.0% 100.0%      1.0  49.0% GLIBC_2.17
     0.0   0.0% 100.0%      1.1  51.0% __libc_start_main
     0.0   0.0% 100.0%      1.0  49.0% _dl_rtld_di_serinfo
     0.0   0.0% 100.0%      1.1  51.0% _start
     0.0   0.0% 100.0%      1.1  51.0% do_something_else
     0.0   0.0% 100.0%      2.1 100.0% imalloc (inline)
     0.0   0.0% 100.0%      2.1 100.0% imalloc_body (inline)
     0.0   0.0% 100.0%      2.1 100.0% je_malloc_default
     0.0   0.0% 100.0%      2.1 100.0% je_prof_backtrace
     
# 解析生成pdf文件
$ jeprof  --show_bytes --pdf testleak jeprof.1384301.0.f.heap > teskleak.pdf
问题根因及解决思路

怀疑:在使用完jemalloc进行分析后发现,程序内存泄露消失了,这是开始怀疑是否真的是glibc中malloc/free没有正确的释放内存给操作系统,导致内存”泄露“问题。

查找glibc malloc内存管理机制,发现是由arena模块进行内存管理。

Arena 设计初衷

  • Arena 机制的目的是减少多线程的锁竞争,每个线程可拥有独立的 arena。
  • 每个 arena 自己维护一个空闲内存池,即使内存没有被程序使用,glibc 也不一定会将内存归还操作系统,而是继续保留这些内存,以便后续分配。

使用如下测试代码块加入到程序中

#include <malloc.h>
#include <stdio.h>

{
    malloc_stats();  // 输出 malloc 的基本统计信息
}

发现,该程序分配了64个arena进行管理内存,与我们知晓的CPU核心数*4完全不相符,我们核心数为8,理应最多分配32个Arena进行管理,并且在每次运行malloc_stats后,in use bytes并没有增多,而system bytes在不停的增加;这就说明了glibc malloc管理的内存池中的空闲字节在不断的增加并没有释放给系统,导致系统内存出现”泄露“现象。

解决方案

方法 1: 调整 MALLOC_ARENA_MAX

MALLOC_ARENA_MAX 控制最大 arena 数量

  • 默认值min(8, 2 * number_of_cores),多核服务器默认会生成多个 arena。

  • 调整方法

    bash复制代码export MALLOC_ARENA_MAX=1
    ./your_program
    

推荐值

  • 对于小内存占用程序MALLOC_ARENA_MAX=1
  • 对于内存敏感型程序MALLOC_ARENA_MAX=2
  • 对于高性能并发程序MALLOC_ARENA_MAX=8(默认值)

⚠️ 风险提示:如果 MALLOC_ARENA_MAX 过小,多线程的内存分配性能会下降,因为多个线程需要竞争同一个 arena 锁


方法 2: 使用 malloc_trim() 主动释放内存

malloc_trim() 函数是 glibc 的内存回收接口,它会将不使用的内存归还操作系统

代码示例
#include <malloc.h>
#include <stdio.h>

int main() {
    void *ptr = malloc(1024 * 1024 * 100); // 100MB
    free(ptr);  // 释放内存,但未归还操作系统
    malloc_trim(0); // 主动归还内存
    printf("Memory returned to system.\n");
    return 0;
}

malloc_trim(0) 会将当前进程中空闲的堆内存归还操作系统,但对使用mmap 分配的大对象不生效

常见的调用位置
  • 在程序的空闲时间调用 malloc_trim(0),以释放内存。
  • 例如,定时器中每隔 30 秒调用一次 malloc_trim(0),用于回收空闲内存。

方法 3: 使用 tcmallocjemalloc 替代 glibc malloc

  • glibc malloc 的 arena 机制适用于一般场景,但在高并发或长时间运行的服务器中,容易产生碎片
  • jemalloc 和 tcmalloc 可以提供更高效的内存管理,并减少空闲内存的浪费
使用 jemalloc
LD_PRELOAD=/usr/lib/libjemalloc.so.2 ./your_program
使用 tcmalloc
LD_PRELOAD=/usr/lib/libtcmalloc.so ./your_program
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值