go内存分配器
阅读 意随行笔记 及draveness关于go的内存管理的部分总结
流程
用户程序(mutator)通过分配器(allocator)从堆(heap)中获得新内存空间,通过收集器(collector)回收空间分配器
go采用空闲链表分配器,分配内存,并且采用隔离适应的方法空闲链表分配器
当用户程序申请内存时,空闲链表分配器会依次遍历空闲的内存块,找到足够大的内存,然后申请新的资源并修改链表意思是说,分配的内存空间必然是连续的,若这块不够,就看下一块够不够,直到找到足够大的一块,而不能是多块凑起来组合的(简而言之就是要连续内存)
隔离适应
将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块分级
- 线程缓存(thread cache)
- 中心缓存(central cache)
- 页堆(page heap)
线程缓存只属于当前线程,不需要加锁所以不存在竞争
32k以上的内存申请需要从页堆里分配
内存管理的基本单位(mspan)
mspan实现了go内存管理的隔离适应效果
每个mspan能分配出去的对象大小(sizeclasses)是固定的
每个mspan里都包含了指向上一个和下一个mspan的指针的字段,且指向的是同一个mcache下的同sizeclasses的内容线程缓存(mcache)
type mcache { ... alloc [numSpanClasses]*mspan //numSpanClasses = 67*2 = 134 ... }
mcache是绑定在GMP的P(调度器)上的
alloc一开始为空的大小为134(67个指针类型的和67个非指针类型的)的数组,使用过程中再去对应规格的中心缓存(mcentral)动态申请mspan中心缓存(mcentral)
type mcentral struct { lock mutex spanclass spanClass //spanClass Id nonempty mSpanList // 所有未被使用的空闲span empty mSpanList // 已经被mcache拿走,未归还的会挂载到这里 nmalloc uint64 //这个mcentral分配mspan的累积计数 }
- 中心缓存是公共区域,多个mcache都可以去申请空间所以得加锁
- 每个中心缓存只管理一种大小(spanclass)的mspan(所以mcentral的总数不超过67 * 2 = 134)
- mcache来申请mspan时,先在nonempty里面看有没有能用的,没有再去empty里面找,都没有就去页堆里面重新再申请一些空间
页堆(mheap)
每个heapArena大小为8B可以管理64M的内存空间
最多有4M个heapArena
所以最多能用256TB
每个指针占8B
所以元信息的大小为4M*8B=32M(也就是要用32M的内存去记录这256T的分配情况)
思考
单个大对象的大小是否不能超过单个heapArena能管理的大小(64M)
否,可以多页储存一个大对象
针对大对象归属的mspan,里面管理的对象大小是不是可能不一样???(参考第六点,是否正确?)
超过32KB大小的由特殊的class表示,该class ID为0,每个class只包含一个对象,所以也满足第六点
一个pages,mspan的个数是不是也只有134个?
mspan里面的元素可能来源于不同的mheap?
本作品采用《CC 协议》,转载必须注明作者和本文链接