目 录CONTENT

文章目录

Arena

FatFish1
2025-02-11 / 0 评论 / 0 点赞 / 43 阅读 / 0 字 / 正在检测是否收录...

了解堆内存,需要了解几个问题:

  • 如何从内核申请堆内存?

  • 谁进行堆内存的管理?

  • 堆内存管理效率是什么决定的?

当下有很多开源版本合历史版本的分配器:

  • 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

也可以直接进宿主机看,参考:

http://www.chymfatfish.cn/archives/k8squestion#%E9%97%AE%E9%A2%984-%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6%E8%AF%BB%E5%86%99%E4%B8%8Eoomkill

0

评论区