[转帖]Jemalloc及内存泄漏分析

jemalloc,内存,泄漏,分析 · 浏览次数 : 0

小编点评

生成内容时需要带简单的排版,例如: ``` for (int i = 0; i < 5; i++) Magic *p = new Magic(); ``` 这可以帮助减少碎片化,提升效率。

正文

https://zhuanlan.zhihu.com/p/138886684

 

不同层面的内存使用

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`

 

这里只截取了输出的一小部分, 它还包括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;

 

 

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的.

 

 

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

extra active pages

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

 

 

当分配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: %p\n", 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: %p\n", 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++里面的虚表等.

引用

与[转帖]Jemalloc及内存泄漏分析相似的内容:

[转帖]Jemalloc及内存泄漏分析

https://zhuanlan.zhihu.com/p/138886684 不同层面的内存使用 Operating System 当程序在执行的时候, 可以通过ps, top等系统命令来预先发现内存泄漏的进程, 比如我们关注的是mysqld, 可以使用如下脚本来监控它的内存使用量. 这样能定位到可

[转帖]【技术剖析】11. 使用jemalloc解决JVM内存泄露问题

https://bbs.huaweicloud.com/forum/thread-169523-1-1.html 作者:王坤 > 编者按:JVM 发生内存泄漏,如何能快速定位到内存泄漏点并不容易。笔者通过使用 jemalloc(可以替换默认的 glibc 库)中的 profiling 机制(通过对程

[转帖]浅谈redis采用不同内存分配器tcmalloc和jemalloc

http://www.kaotop.com/it/173669.html 我们知道Redis并没有自己实现内存池,没有在标准的系统内存分配器上再加上自己的东西。所以系统内存分配器的性能及碎片率会对Redis造成一些性能上的影响。 在Redis的 zmalloc.c 源码中,我们可以看到如下代码: ?

[转帖]jemalloc内存分配算法

https://www.cnblogs.com/xiaojiesir/p/15450732.html jemalloc内存分配算法简介 jemalloc 是由 Jason Evans 在 FreeBSD 项目中引入的新一代内存分配器。它是一个通用的 malloc 实现,侧重于减少内存碎片和提升高并发

[转帖]jemalloc 性能测试

https://wenfh2020.com/2020/07/30/jemalloc/ jemalloc 是一个优秀的内存分配器,通过与系统默认的内存分配器进行比较:jemalloc 内存分配性能比系统默认的分配器快 50%。 1. 安装 2. 测试 2.1. 源码 (github 测试源码) 2.2

[转帖]ARM64 CentOS系统下MySQL使用jemalloc时的问题和解决方法

https://aijishu.com/a/1060000000321521 本文主要介绍在ARM64 CentOS系统下,MySQL使用jemalloc作为内存管理器时,内存占用问题的分析过程和解决方法。 Jemalloc 简介 Jemalloc是由Jason Evans在FreeBSD项目中引入

[转帖]

Linux ubuntu20.04 网络配置(图文教程) 因为我是刚装好的最小系统,所以很多东西都没有,在开始配置之前需要做下准备 环境准备 系统:ubuntu20.04网卡:双网卡 网卡一:供连接互联网使用网卡二:供连接内网使用(看情况,如果一张网卡足够,没必要做第二张网卡) 工具: net-to

[转帖]

https://cloud.tencent.com/developer/article/2168105?areaSource=104001.13&traceId=zcVNsKTUApF9rNJSkcCbB 前言 Redis作为高性能的内存数据库,在大数据量的情况下也会遇到性能瓶颈,日常开发中只有时刻

[转帖]ISV 、OSV、 SIG 概念

ISV 、OSV、 SIG 概念 2022-10-14 12:29530原创大杂烩 本文链接:https://www.cndba.cn/dave/article/108699 1. ISV: Independent Software Vendors “独立软件开发商”,特指专门从事软件的开发、生产、

[转帖]Redis 7 参数 修改 说明

2022-06-16 14:491800原创Redis 本文链接:https://www.cndba.cn/dave/article/108066 在之前的博客我们介绍了Redis 7 的安装和配置,如下: Linux 7.8 平台 Redis 7 安装并配置开机自启动 操作手册https://ww