Go内存分配 | 青训营笔记

106 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第6天

Go内存分配

go的内存分配器与Google的tcmalloc类似, 简单说就是维护一大块的全局内存, 每个线程维护一块小的私有内存, 私有内存不足时再从全局内存中申请 .

分块

go是提前将内存分块, 当对象申请空间时, 分配与之接近的一个块.

  1. 调用mmap()系统调用, 向OS申请一大块内存, 例如4 MB
  2. 先将内存划分为大块, 例如8 KB, 称为mspan
  3. 再将mspan大块继续划分为特定大小的小块(也称为object), 用于对象分配, 这些小块即可用于对象内存分配
  4. noscan mspan, 分配不包含指针的对象, 即GC不需要扫描
  5. scan mspan, 分配包含指针的对象, GC需要扫描

image-20230206215223569

图1 每个mspan均分为特定大小的块, 用于对象内存分配

另外, span是由多个地址连续的页(page)组成, 分配器按照页数来区分不同大小的span, 需要时便按照页数为索引进行查找. 在查找闲置span没有找到大小合适的时候, 会引发裁剪操作, 即从页数更多的span中裁剪出合适的大小, 剩余的部分继续放入span管理器中. 并且管理器会将地址相邻的span进行合并, 减少内部碎片.

object按照8字节的倍数可以分为多种, 例如, 某对象大小为17字节, 那么分配器将会为他分配大小为24字节的object, 虽然造成了一定的空间浪费, 但是优化了分配和复用管理策略, 是空间换时间的一种做法.

分配器也会尝试将多个微小对象组合, 放入到一个object中, 以节约内存.

缓存

go内存分配器设置了多级缓存用于分配内存

go的内存分配器主要有三种组件组成, cache, central以及heap

  • cache : 每个运行期工作线程都会绑定一个cache, 用于无锁object分配
  • central : 为所有cache提供分配好的备用span资源(cache中合适的span用完时使用)
  • heap : 管理闲置span, 需要时会向系统申请新的内存

需要注意的是, cache是每个线程独有的, 私有, 不分享

而central是所有工作线程共享的.

heap中存放着一些闲置的span(没有划分为object), 当central中也不存在空闲的span时, 会将这些闲置的span进行划分为object

工作线程私有且不被共享的cache是go实现共性能内存分配的核心, 而central是在多个cache之间提高object的利用率, 避免内存浪费.

大对象直接从heap中分配回收

image-20230206222348886