这是我参与「第五届青训营 」伴学笔记创作活动的第6天
Go内存分配
go的内存分配器与Google的tcmalloc类似, 简单说就是维护一大块的全局内存, 每个线程维护一块小的私有内存, 私有内存不足时再从全局内存中申请 .
分块
go是提前将内存分块, 当对象申请空间时, 分配与之接近的一个块.
- 调用mmap()系统调用, 向OS申请一大块内存, 例如4 MB
- 先将内存划分为大块, 例如8 KB, 称为mspan
- 再将mspan大块继续划分为特定大小的小块(也称为object), 用于对象分配, 这些小块即可用于对象内存分配
- noscan mspan, 分配不包含指针的对象, 即GC不需要扫描
- scan mspan, 分配包含指针的对象, GC需要扫描
图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中分配回收