了解堆内存,需要了解几个问题:
如何从内核申请堆内存?
谁进行堆内存的管理?
堆内存管理效率是什么决定的?
当下有很多开源版本合历史版本的分配器:
dlmalloc – 第一个被广泛使用的通用动态内存分配器;
ptmalloc2 – glibc 内置分配器的原型;
jemalloc – FreeBSD & Firefox 所用分配器;
tcmalloc – Google 贡献的分配器;
libumem – Solaris 所用分配器;
多线程申请堆内存
在内存管理领域,我们一般用「堆」指代用于分配动态内存的虚拟地址空间,而用「栈」指代用于分配静态内存的虚拟地址空间。具体到虚拟内存布局(Memory Layout),堆维护在通过brk系统调用申请的「Heap」及通过mmap系统调用申请的「Memory Mapping Segment」中;而栈维护在通过汇编栈指令动态调整的「Stack」中。在 Glibc 里,「Heap」用于分配较小的内存及主线程使用的内存。
linux的内存分配器从早期的dlmalloc转为了ptmallc2,即glibc内存分配器的原型版本,因为ptmalloc2支持多线程,二者的区别是:
在 dlmalloc 中,当两个线程同时
malloc
时,只有一个线程能够访问临界区(critical section)——这是因为所有线程共享用以缓存已释放内存的「空闲列表数据结构」(freelist data structure),所以使用 dlmalloc 的多线程应用会在malloc
上耗费过多时间,从而导致整个应用性能的下降在 ptmalloc2 中,当两个线程同时调用
malloc
时,内存均会得以立即分配——每个线程都维护着单独的堆,各个堆被独立的空闲列表数据结构管理,因此各个线程可以并发地从空闲列表数据结构中申请内存。这种为每个线程维护独立堆与空闲列表数据结构的行为就「per thread arena」
假设一个程序有一个main thread和一个新线程sub thread,使用ptmalloc2申请内存,分别观察两个线程执行malloc的状态:
main线程申请了1000字节内存,这时通过移动Program Break位置(又叫
brk
中断)分配了内存(0x0804b000 - 0x0806c000这种类似的数据段),尽管只申请了1000字节,但实际分配了132KB的堆,这个区域就是一个arena,因为是主线程申请的,因此叫做main arena,接下来主线程申请内存会继续分配这个132KB的arena中的剩余部分,当分配完毕,Program Break位置继续移动,进而扩容main线程执行free操作,它对应的arena并不会立即归还给操作系统,而是移交给分配器,这块内存被添加到「main arenas bin」中(在 glibc malloc 中,空闲列表数据结构被称为「bin」)。随后当用户请求内存时,分配器就不再向内核申请新堆了,而是先试着各个「bin」中查找空闲内存。只有当 bin 中不存在空闲内存时,分配器才会继续向内核申请内存。
sub线程申请了1000字节,通过sbrk系统调用(b7500000 - b7521000这种类似的数据段),虽然还是只申请了1000字节,但是实际映射到地址空间有1MB,其中132KB被设置读写权限,作为该sub线程的堆内存,这132KB被称为thread arena
sub线程执行free操作,也不会把内存归还给操作系统,而是移交给分配器,然后添加在了「thread arenas bin」中
在k8s环境下,可以进入pod,执行cat /sys/fs/cgroup/memory/memory.stat
可以看到如下:
inactive_anon 0
active_anon 17377849344
total_inactive_anon 0
total_active_anon 17377849344
也可以直接进宿主机看,参考:
评论区