当前位置: 首页 > 工具软件 > log-malloc2 > 使用案例 >

Linux虚拟内存介绍,以及malloc_stats和malloc_info 监控查看内存情况

长孙景天
2023-12-01

Linux虚拟内存介绍,以及malloc_stats和malloc_info 监控查看内存情况

zzhongcy 2019-04-09 10:33:08 3106 已收藏 3
分类专栏: Linux
版权
查找内存泄漏问题,可以使用valgrind、malloc_stats和malloc_info 监控查看内存情况。

1、 Linux内存介绍
1.1 Linux 的虚拟内存管理有几个关键概念:
1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址;
2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址;
3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

1.2、Linux 虚拟地址空间如何分布?
Linux 使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为:
1、只读段:该部分空间只能读,不可写;(包括:代码段、rodata 段(C常量字符串和#define定义的常量) )
2、数据段:保存全局变量、静态变量的空间;
3、堆 :就是平时所说的动态内存, malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。
4、文件映射区域 :如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。
5、栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit –s 查看。
6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。

1.3 32 位系统有4G 的地址空间::
其中 0x08048000~0xbfffffff 是用户空间,0xc0000000~0xffffffff 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk 函数控制堆顶_edata往高地址方向变化。

1.4 64位系统结果怎样呢? 64 位系统是否拥有 2^64 的地址空间吗?
事实上, 64 位系统的虚拟地址空间划分发生了改变:
1、地址空间大小不是232,也不是264,而一般是2^48。因为并不需要 2^64 这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,这可通过 /proc/cpuinfo 来查看
address sizes : 40 bits physical, 48 bits virtual

  2、其中,0x0000000000000000~0x00007fffffffffff 表示用户空间, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF 表示内核空间,共提供 256TB(2^48) 的寻址空间。
  这两个区间的特点是,第 47 位与 48~63 位相同,若这些位为 0 表示用户空间,否则表示内核空间。 

  3、用户空间由低地址到高地址仍然是只读段、数据段、堆、文件映射区域和栈;

2、 valgrind
valgrind可以用来检测内存泄露,但在使用中,往往会遇到一些问题,给调试工作带来很多不必要的麻烦,我自己遇到的有以下两种:

  (1)内存泄露误检(系统初始化时,可能有一些需要长期保存在内存中的数据结构,这些空间是永远不释放的,而这些内存会被认为绝对泄露)

  (2) valgrind检查内存泄露过于全面,运行后的结果太多往往很难从中找到有用的信息。有时候,我们只需要关注某些函数,可能在执行某个操作,调用某些函数时会出现内存泄露,此时,valgrind的工作显得冗余而复杂   

3、 mallinfo
mallinfo函数已不推荐使用,并且都不再更新。

可以添加代码到程序:

#include <malloc.h>
#include <stdio.h>
void dumpMallinfo(void) {
struct mallinfo m = mallinfo();
printf(“uordblks = %dnfordblks = %dn”, m.uordblks, m.fordblks);
}
在GDB,可以 call dumpMallinfo().

4、 malloc_stats
系统库函数中提供了malloc_stats()函数,可以统计本进程具体的内存使用情况,精确到字节。

glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况。

struct mallinfo {
int arena; /* non-mmapped space allocated from system /
int ordblks; /
number of free chunks /
int smblks; /
number of fastbin blocks /
int hblks; /
number of mmapped regions /
int hblkhd; /
space in mmapped regions /
int usmblks; /
maximum total allocated space /
int fsmblks; /
space available in freed fastbin blocks /
int uordblks; /
total allocated space /
int fordblks; /
total free space /
int keepcost; /
top-most, releasable (via malloc_trim) space */
};

/*返回heap(main_arena)的内存使用情况,以 mallinfo 结构返回 */
struct mallinfo mallinfo();

/* 将heap和mmap的使用情况输出到stderr*/
void malloc_stats();

4.1 gdb内部调试
call malloc_stats()
(gdb) call malloc_stats()
Arena 0:
system bytes = 135168
in use bytes = 96
Total (incl. mmap):
system bytes = 135168
in use bytes = 96
max mmap regions = 0
max mmap bytes = 0
call malloc_info(0, stdout)
(gdb) call malloc_info(0, stdout)














<system type=“current” size="135168
/>
<system type=“max” size="135168
/>


4.2 gdb外部调试
根据 malloc_stats()的手册手册,内存信息被发送到标准错误。 一般输出到stderr 。

命名:

gdb --batch --pid --ex ‘call malloc_stats()’
查看stderr.log:


MALLOC: 11159496 ( 10.6 MiB) Bytes in use by application
MALLOC: + 1769472 ( 1.7 MiB) Bytes in page heap freelist
MALLOC: + 3580792 ( 3.4 MiB) Bytes in central cache freelist
MALLOC: + 1998848 ( 1.9 MiB) Bytes in transfer cache freelist
MALLOC: + 19240128 ( 18.3 MiB) Bytes in thread cache freelists
MALLOC: + 1379480 ( 1.3 MiB) Bytes in malloc metadata
MALLOC: ------------
MALLOC: = 39128216 ( 37.3 MiB) Actual memory used (physical + swap)
MALLOC: + 0 ( 0.0 MiB) Bytes released to OS (aka unmapped)
MALLOC: ------------
MALLOC: = 39128216 ( 37.3 MiB) Virtual address space used
MALLOC:
MALLOC: 810 Spans in use
MALLOC: 157 Thread heaps in use
MALLOC: 32768 Tcmalloc page size

Call ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).
Bytes released to the OS take up virtual address space but no physical memory.
4.3 其他
如果默认编译,使用的是libc的ptmalloc内存分配器库,这个库可能有一定的内存碎片问题,我们线上也有遇到过,见comment最后
任然无法查出,得上通用的内存分配扫描工具 。比如systap脚本抓取malloc/free的栈记录,然后分析记录信息查看。比如valgrind --mem-leak(往上可以搜索方法, 注意)
4.3.1 glibc库的ptmalloc2信息采集:
强制nginx worker调用malloc_stats()函数,让其将数据输出到stderr(nginx重定向到error.log)

$ gdb -p $(pgrep -P $(cat logs/nginx.pid)) -ex ‘call malloc_stats()’

error.log内dump出来的信息

Arena 0: <<< 线程 0(一般tengine worker内只有1个thread)
system bytes = 1372160 <<< 从os抽取的内存(一般为mmap)
in use bytes = 1198448 <<< 应用程序malloc的
Total (incl. mmap):
system bytes = 1372160
in use bytes = 1198448
max mmap regions = 6
max mmap bytes = 2789376
4.3.2 for jemalloc:
$ gdb -batch -p -ex ‘call malloc_stats_print(0,0,0)’
如果想知道堆内片究竟有多碎 ,可通过 mallinfo 结构中的 fsmblks 、 smblks 、 ordblks值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节, 80~512 字节,512~128k 。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。

  不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!

5、 除了 glibc 的 malloc/free ,还有其他第三方实现吗?
其实,很多人开始诟病 glibc 内存管理的实现,就是在高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换 glibc 的实现,最著名的当属 google 的tcmalloc 和 facebook 的 jemalloc 。网上有很多资源,可搜索之,这里就不详述了。

6、 既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢?
既然堆内碎片不能直接释放,导致疑似“内存泄露”问题,为什么 malloc 不全部使用 mmap 来实现呢(mmap分配的内存可以会通过 munmap 进行 free ,实现真正释放)?而是仅仅对于大于 128k 的大块内存才使用 mmap ?

    其实,进程向 OS 申请和释放地址空间的接口 sbrk/mmap/munmap 都是系统调用,频繁调用系统调用都比较消耗系统资源的。并且, mmap 申请的内存被 munmap 后,重新申请会产生更多的缺页中断。例如使用 mmap 分配 1M 空间,第一次调用产生了大量缺页中断 (1M/4K 次 ) ,当munmap 后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。
    同时堆是一个连续空间,并且堆内碎片由于没有归还 OS ,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低 CPU 的消耗。 因此, glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD, <SIZE>) 来修改这个临界值。

malloc系统有自己的内存池管理策略:
1、malloc的时候,检测池中是否有足够内存,有则直接分配,无则从内存中调用brk/mmap函数分配,一般小于等于128k(可设置)的内存,使用brk函数,此时堆向上(有人有的硬件或系统向下)增长,大于128k的内存使用mmap函数申请,此时堆的位置任意,无固定增长方向。

 2、free的时候,检测标记是否是mmap申请,是则调用unmmap归还给操作系统,非则检测堆顶是否有大于128k的空间,有则通过brk归还给操作系统,无则标记未使用,仍在glibc的管理下。

  glibc为申请的内存存储多余的结构用于管理,因此即使是malloc(0),也会申请出内存(一般16字节,依赖于malloc的实现方式),在应用程序层面,malloc(0)申请出的内存大小是0,因为malloc返回的时候在实际的内存地址上加了16个字节偏移,而c99标准则规定malloc(0)的返回行为未定义。除了内存块头域,malloc系统还有红黑树结构保存内存块信息,不同的实现又有不同的分配策略。频繁直接调用malloc,会增加内存碎片,增加和内核态交互的可能性,降低系统性能。

7、如何查看进程的缺页中断信息?
可通过以下命令查看缺页中断信息

ps -o majflt,minflt -C <program_name>
ps -o majflt,minflt -p
MAJFLT MINFLT
0 27698
其中:: majflt 代表 major fault ,指大错误; minflt 代表 minor fault ,指小错误。

这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
其中 majflt 与 minflt 的不同是:

    majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要load 到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。

参考:

https://github.com/alibaba/tengine/issues/1043

https://blog.csdn.net/origin_lee/article/details/42740535

http://landcareweb.com/questions/20566/jian-cha-gdbzhong-de-c-c-dui-nei-cun-tong-ji-xin-xi

 类似资料: