valgrind 内存泄漏_Jemalloc及内存泄漏分析

不同层面的内存使用

Operating System

当程序在执行的时候, 可以通过ps, top等系统命令来预先发现内存泄漏的进程, 比如我们关注的是mysqld, 可以使用如下脚本来监控它的内存使用量. 这样能定位到可疑进程, 它占了多少内存, 它的内存是否在上涨.

#!/bin/bash

largest=70

while :; do
    mem=$(ps -p `pidof mysqld` -o %mem | tail -1)
    imem=$(printf %.0f $mem)
    if [ $imem -gt $largest ]; then
        largest=$imem
        echo `date`, $largest >> /tmp/large_mem.log
    fi
    sleep 10
done

即使对可疑进程的实现一无所知, 站在系统层面也是有办法定位问题的. 内存泄漏一般来说就是malloc/free不匹配导致 (其他分配释放函数类似), 只要能找出这种情况即可, 比如ebpf实现的memleak就是这样实现的. 为了减少篇幅, 下面只列了memleak的核心逻辑. 只要能找出泄漏内存的调用栈, 就基本定位问题了, 当然找到调用栈也不是必要条件.

static inline int gen_alloc_exit2(struct pt_regs *ctx, u64 address) {
        info.timestamp_ns = bpf_ktime_get_ns();
        info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
        allocs.update(&address, &info);
        update_statistics_add(info.stack_id, info.size);
}

static inline int gen_free_enter(struct pt_regs *ctx, void *address) {
        allocs.delete(&addr);
        update_statistics_del(info->stack_id, info->size);
}

还有其他各种各样的系统工具可用于内存泄漏定位, 比如ASAN, Valgrind等, 但是通过ebpf可以在程序正常运行时随时进行调试, 是更为通用的方法. 在没有ebpf支持的场合, 也可以利用systemtap代替.

malloc lib

应用程序一般情况下会通过malloc库来申请/释放内存, 这里假设我们使用jemalloc, jemalloc从性能到调试都是目前比较好的选择. 当我们定位到可疑进程后, 怎么更进一步定位到问题? jemalloc提供了几种手段:

  • profile. 和上面的memleak类似, jemalloc也可以在每次malloc/free的地方收集调用栈, 如果每次搜集调用栈对性能影响太大, 也可以采样搜集. 具体使用方法这里不表
  • jemalloc自己的统计手段. 如果程序在运行, 我们可以简单通过gdb的函数打印
echo 'p malloc_stats_print(0,0,0)' | gdb --quiet -nx -p `pidof mysqld`

ec425e478fbd8ccc35d6a71566e40cb3.png

这里只截取了输出的一小部分, 它还包括jemalloc的版本, 配置选项等等. 通过jemalloc的输出, 可以看出是哪个大小的bin出问题, 比如是2560大小的分配太多, 还是2MB的分配出了问题. 当问题局限到某一个bin的时候, 特别是bin size比较大的情况, 开发者如果对代码比较熟悉, 定位还是比较简单的.

另外一个问题是, 既然ebpf能够在系统运行时搜集所有的分配释放的调用栈, malloc lib是不是也可以做类似的事情用于定位内存泄漏?

Application

一般比较成熟的应用都会内置自己的内存统计信息, 比如MySQL SQL层的内存统计, 从调试角度看这种方式的好处是开销很低, 只要对应用比较熟悉, 不需要借助其他系统的手段, 在很多时候也能较快定位问题.

  1. 通过performance schema可以得到SQL内存的统计信息, 首先需要在my.cnf中enable performance schema:
performance_schema=ON
  1. 并且在mysql命令行中enable相应的内存统计:
UPDATE setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%';
  1. 然后就可以获取内存统计信息
SELECT EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED/1024/1024 FROM memory_summary_global_by_event_name WHERE EVENT_NAME like 'memory/sql/%' order by CURRENT_NUMBER_OF_BYTES_USED desc limit 10;

a66c11be6a34e7a456c17e619b161d0f.png

Jemalloc的开销

应用自己统计的内存信息可能和malloc lib统计的不一致, 可能原因如下:

  • 应用统计不全
  • 应用使用了mmap, 不经过malloc lib
  • malloc lib自己的开销应用不能感知, 我们现在讨论这个

metadata

jemalloc为了管理内存需要额外的metadata, 这些metadata需要占用物理内存:

Allocated: 52695496136, active: 60743540736, metadata: 449655848 (n_thp 0), resident: 61901737984, mapped: 62091505664, retained: 12955602944

jemalloc 5.0对metadata有较大改动, 之前使用chunk(一般为2MB)为单位管理内存, 5.0之后默认使用extent. 测试结果显示, 新的metadata大小显著减少.

dirty pages

当Application通过free释放内存的时候, 这些内存可能并没有通过munmap或者madivse(以page为单位)返还给OS. 这样做的好处是, reuse这片内存不需要page fault到OS kernel申请内存, 当然坏处就是可能造成内存浪费.

decaying:  time       npages       sweeps     madvises       purged
   dirty:   N/A      4129045       268681     34176851    367223631

我们主要关注dirty npages, 这里可以看到4129045个dirty pages, 这大约16GB内存是预期之外的.

slab utilization

当Application通过free释放内存的时候, 如果只是free了一个page的一部分, 或者slab的一部分, 这个时候肯定不会返还给OS的.

8504fec168bb0b13417f423876846682.png

还有一些fragmentation, 比如申请9字节, 实际分配了16字节, 这个在jemalloc log中都有反映.

extra active pages

除去以上的内存, active还是远大于allocated内存, 我们需要理解这些额外的内存到底什么用途.

f30fb20197b5a541b08654b1daf55274.png

当分配large object (>=16KB) 的时候, 默认情况下jemalloc并不会分配对齐的地址, 导致额外的空间开销.

const int N = 16;
void *buf[N];
int i;

for (i = 0; i < N; i++) { buf[i] = malloc(0x4000); }
for (i = 0; i < N - 1; i++) {
    printf("diff-16k: %lx, base: %pn", buf[i + 1] - buf[i], buf[i]);
}
for (i = 0; i < N; i++) { free(buf[i]); }

for (i = 0; i < N; i++) { buf[i] = malloc(0x2000); }
for (i = 0; i < N - 1; i++) {
    printf("diff-8k: %lx, base: %pn", buf[i + 1] - buf[i], buf[i]);
}
for (i = 0; i < N; i++) { free(buf[i]); }

运行情况如下:

$ LD_PRELOAD=libjemalloc.so ./a.out 2>&1 | tee 1
diff-16k: 5840, base: 0x7f1231053480
diff-16k: 4780, base: 0x7f1231058cc0
diff-16k: 4e00, base: 0x7f123105d440
diff-16k: 5a00, base: 0x7f1231062240
diff-16k: 4780, base: 0x7f1231067c40
diff-16k: 5840, base: 0x7f123106c3c0
diff-16k: 45c0, base: 0x7f1231071c00
diff-16k: 4f80, base: 0x7f12310761c0
diff-16k: 5a00, base: 0x7f123107b140
diff-16k: 4580, base: 0x7f1231080b40
diff-16k: 5c80, base: 0x7f12310850c0
diff-16k: 4400, base: 0x7f123108ad40
diff-16k: 50c0, base: 0x7f123108f140
diff-16k: 5600, base: 0x7f1231094200
diff-16k: 55c0, base: 0x7f1231099800
diff-8k: 2000, base: 0x7f12310a3000
diff-8k: 2000, base: 0x7f12310a5000
diff-8k: 2000, base: 0x7f12310a7000
diff-8k: 2000, base: 0x7f12310a9000
diff-8k: 2000, base: 0x7f12310ab000
diff-8k: 2000, base: 0x7f12310ad000
diff-8k: 2000, base: 0x7f12310af000
diff-8k: 2000, base: 0x7f12310b1000
diff-8k: 2000, base: 0x7f12310b3000
diff-8k: 2000, base: 0x7f12310b5000
diff-8k: 2000, base: 0x7f12310b7000
diff-8k: 2000, base: 0x7f12310b9000
diff-8k: 2000, base: 0x7f12310bb000
diff-8k: 2000, base: 0x7f12310bd000
diff-8k: 2000, base: 0x7f12310bf000

减少jemalloc自身的内存开销

  • metadata: 可以通过升级到新版jemalloc减少
  • dirty pages: 可以尝试多种途径
  • 设置dirty_decay_ms=0, 这种方法完全消除dirty pages, 不过对性能可能会有影响
  • 减少arena number, 因为purge dirty pages的执行点是N次进入jemalloc的函数并且是arena级别, 减少arena的个数应该能够有所帮助
  • 设置background_thread, 通过后台线程purge, 从而避免因为arena当前没有足够的内存操作delay purge
  • jemalloc提供了手动purge的函数
  • slab utilization: 这个可做的不多
  • fragmentation: 减少fragmentation大小的分配, 比如尽量减少16KB+1的malloc
  • extra active pages: 可以尝试2种办法, 需要性能验证
  • jemalloc --disable-cache-oblivious
  • 使用对齐的内存分配方法, 比如posix_memalign

从内容的角度

上面主要是从分配释放角度去定位内存泄漏问题, 很多时候需要进程还是活的, 如果只有core文件呢? 首先我们还是有办法解析出来里面jemalloc的统计信息, 我们可以基本定位到泄漏的bin size, 如果能够扫描该bin size的所有内容, 是不是有机会发现更多的蛛丝马迹? 最直接的一个想法是, 如果每个泄漏的对象都带一个标记, 比如magic number的话, 只要能在core里面找到大量的该magic number, 那么就能基本定位泄漏的对象, 从而最终定位泄漏的问题

struct Magic {
        Magic() : magic(0x1234) {}
        int magic;
};

int main() {
        for (int i = 0; i < 5; i++)
                Magic *p = new Magic();
        return 0;
}

在core里, 我们可以找到magic 0x1234的位置

(gdb) find /w 0x7ffff6e00000,0x7ffff7600000,0x1234
0x7ffff7208008
0x7ffff7208010
0x7ffff7208018
0x7ffff7208020
0x7ffff7208028

当然上面是通过正面找的方式, 也就是说已经知道0x1234已经泄漏了, 很多时候我们其实并不知道是哪里泄漏了, 一堆内存在那怎么找呢? 最暴力的方式, 比如按照4字节为单位扫描所有内存, 再计算它们的个数, 这其实是一种可行的方法. 但这种办法依赖一个前提, 每个数据结构有自己的标识, 比如上面的magic number, 或者c++里面的虚表等.

引用

http://jemalloc.net/jemalloc.3.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值