go 内存管理
以后都在 github 更新,请戳 go 内存管理实现
目录
相关位置文件
- src/runtime/mgc.go
- src/runtime/malloc.go
- src/runtime/mgcmark.go
- src/runtime/mbitmap.go
概览
当你在 go 中申请内存空间时, 申请的内存空间直接来源于 span
, 通常一个 span
存储了 8kb 大小的空间, span
中的每个元素大小是 N
, 那么一个 span
就含有 8kb / N
个元素, alloc
会在 span
中获取一个空闲的元素
mcache
缓存了一些 span
, mcache
是每个线程(在Go 里, 是每个 P)独有的一个线程缓存的结构, 在当前的 P
中缓存 136 个不同的 span
, 每当你需要一个 span
时, 你可以在不加锁的情况下快速从这里获取到一个
如果当前需要的span
在 macache
中获取不到, 则会从 mcentral
中获取一个新的 span
, mcentral
存储了两个 span
的双链表, mcentral
是存储在 heap
中的全局变量, 获取时会在上述链表中查找到一个空闲的 span
如果mcentral
也获取不到, 则会从 P
的缓存中获取, P
的缓存中的的 span
是从 heap
中申请的
arena
存储了 heap
空间中的元信息, 每个 arena
对象代表了 64MB 的空间的信息, 并且还有一个 bitmap
代表当前的空间中每个字节是否需要扫描, 是否存储了指针的标记信息
Go 把heap中所有的虚拟地址映射到了 arena
数组中不同的对象中, 当你拿到一个对象的地址时, 你可以通过 (addr - base_addr) / range_length
简单的计算就获取到对应的 arena
数组索引, 从而获得 arena
对象
映射(申请内存) 是通过 mmap
系统调用 实现的, 这个申请并不会 实际占用物理存储空间
在 go 1.11 之后, 不同的 arena
中的地址不再是连续的
span
每一个 P
都存储了一个指向 mcache
的指针, 这个对象缓存了一个列表, 列表里是元素大小不同的 span
// src/runtime/mcache.go
func allocmcache() *mcache {
var c *mcache
systemstack(func() {
lock(&mheap_.lock)
c = (*mcache)(mheap_.cachealloc.alloc())
c.flushGen = mheap_.sweepgen
unlock(&mheap_.lock)
})
for i := range c.alloc {
c.alloc[i] = &emptymspan
}
c.nextSample = nextSample()
return c
}
numSpanClasses
默认是 136, mcache
中的 alloc
数组存储了 136 种不同的 span
, alloc[2]
和 alloc[3]
大小相同, 一个是给 没有指针的对象 (noscan
) 用的, 一个是给包含指针的对象(scan
)用的, alloc[4]
和 alloc[5]
大小相同, 以此类推
这些不同的 span
元素大小从 8bytes 到 32kb 不等, 列表中的基数个用作 noscan
, 偶数个用作 scan
span
并没有在程序启动时就初始化好, 它是动态申请的(在你申请对应大小空间的时候进行申请的)
// src/runtime/sizeclasses.go
// class bytes/obj bytes/span objects tail waste max waste min align
// 1 8 8192 1024 0 87.50% 8
// 2 16 8