文章目录
内存管理
内存分配器
线性分配(Bump Allocator)
指针指向空闲内存地址,修改更新指针来分配下一个内存地址。
- 复杂度低,执行效率高
- 内存被回收后无法重用
- 需要垃圾回收机制,标记/复制/分代回收算法整理内存碎片
- c和c++对外暴露指针,无法使用。
空闲链表分配(Free-List Allocator)
用链表结构管理空闲内存块,通过修改更新链表连接情况分配内存地址。
- 方便可进行内存回收重用
- 分配内存检索是O(n)
- 首次适应(First-Fit),链表头遍历选择第一个大于申请内存的内存块
- 循环首次适应(Next-Fit),从上一个遍历结束地址,选择第一个大于申请内存的内存块
- 最优适应(Best-Fit),链表头遍历,选择最合适的内存块
- 隔离适应(Segregated-Fit),内存分割多个连表,每个连表内存块大小相同,申请内存先找合适连表,在找合适内存块
runtime/mfixalloc.go
// 空闲链表分配器 最优适应
type fixalloc struct {
size uintptr // 对象大小
first func(arg, p unsafe.Pointer) // called first time p is returned
arg unsafe.Pointer
list *mlink
chunk uintptr // use uintptr instead of unsafe.Pointer to avoid write barriers
nchunk uint32
inuse uintptr // in-use bytes now
stat *sysMemStat
zero bool // zero allocations
}
线程缓存分配 (Thread-Caching Malloc,TCMalloc)
go语言内存分配器,借鉴TCMalloc,多级缓冲根据对象大小采取不同分配策略
对象大小
类别 | 大小 |
---|---|
微对象 | (0, 16B) |
小对象 | [16B, 32KB] |
大对象 | (32KB, +∞) |
runtime/sizeclasses.go
// classid 每span结构都有一个classid
// bytes/obj class对象的字节数
// bytes/span 每span占用堆的字节数(pagenum*pagesize)
// object 每span可分配的对象数(bytes/spans)/(bytes/obj)
// waste bytes 每span产生的内存碎片(bytes/spans)%(bytes/obj)
// class bytes/obj bytes/span objects tail waste max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 24 8192 341 8 29.24%
// 4 32 8192 256 0 21.88%
// 5 48 8192 170 32 31.52%
// 6 64 8192 128 0 23.44%
// 7 80 8192 102 32 19.07%
// 8 96 8192 85 32 15.95%
// 9 112 8192 73 16 13.56%
// 10 128 8192 64 0 11.72%
// 11 144 8192 56 128 11.82%
// 12 160 8192 51 32 9.73%
// 13 176 8192 46 96 9.59%
// 14 192 8192 42 128 9.25%
// 15 208 8192 39 80 8.12%
// 16 224 8192 36 128 8.15%
// 17 240 8192 34 32 6.62%
// 18 256 8192 32 0 5.86%
// 19 288 8192 28 128 12.16%
// 20 320 8192 25 192 11.80%
// 21 352 8192 23 96 9.88%
// 22 384 8192 21 128 9.51%
// 23 416 8192 19 288 10.71%
// 24 448 8192 18 128 8.37%
// 25 480 8192 17 32 6.82%
// 26 512 8192 16 0 6.05%
// 27 576 8192 14 128 12.33%
// 28 640 8192 12 512 15.48%
// 29 704 8192 11 448 13.93%
// 30 768 8192 10 512 13.94%
// 31 896 8192 9 128 15.52%
// 32 1024 8192 8 0 12.40%
// 33 1152 8192 7 128 12.41%
// 34 1280 8192 6 512 15.55%
// 35 1408 16384 11 896 14.00%
// 36 1536 8192 5 512 14.00%
// 37 1792 16384 9 256 15.57%
// 38 2048 8192 4 0 12.45%
// 39 2304 16384 7 256 12.46%
// 40 2688 8192 3 128 15.59%
// 41 3072 24576 8 0 12.47%
// 42 3200 16384 5 384 6.22%
// 43 3456 24576 7 384 8.83%
// 44 4096 8192 2 0 15.60%
// 45 4864 24576 5 256 16.65%
// 46 5376 16384 3 256 10.92%
// 47 6144 24576 4 0 12.48%
// 48 6528 32768 5 128 6.23%
// 49 6784 40960 6 256 4.36%
// 50 6912 49152 7 768 3.37%
// 51 8192 8192 1 0 15.61%
// 52 9472 57344 6 512 14.28%
// 53 9728 49152 5 512 3.64%
// 54 10240 40960 4 0 4.99%
// 55 10880 32768 3 128 6.24%
// 56 12288 24576 2 0 11.45%
// 57 13568 40960 3 256 9.99%
// 58 14336 57344 4 0 5.35%
// 59 16384 16384 1 0 12.49%
// 60 18432 73728 4 0 11.11%
// 61 19072 57344 3 128 3.57%
// 62 20480 40960 2 0 6.87%
// 63 21760 65536 3 256 6.25%
// 64 24576 24576 1 0 11.45%
// 65 27264 81920 3 128 10.00%
// 66 28672 57344 2 0 4.91%
// 67 32768 32768 1 0 12.50%
多级缓存
- 线程缓存(Thread Cache)
- 线程内无竞争无锁
- 内存空间有限,负责小对象创建,大对象直接选择页堆分配
- 中心缓存(Central Cache)
- 页堆(Page Heap)
地址空间状态转移
状态
状态 | 解释 |
---|---|
None | 内存没有被保留或者映射,是地址空间的默认状态 |
Reserved | 运行时持有该地址空间,但是访问该内存会导致错误 |
Prepared | 内存被保留,一般没有对应的物理内存访问该片内存的行为是未定义的可以快速转换到 Ready 状态 |
Ready | 可以被安全访问 |
状态转移函数
名称 | 解释 |
---|---|
runtime.sysAlloc | 会从操作系统中获取一大块可用的内存空间,可能为几百 KB 或者几 MB; |
runtime.sysFree | 会在程序发生内存不足(Out-of Memory,OOM)时调用并无条件地返回内存; |
runtime.sysReserve | 会保留操作系统中的一片内存区域,访问这片内存会触发异常; |
runtime.sysUsed | 通知操作系统应用程序需要使用该内存区域,保证内存区域可以安全访问; |
runtime.sysUnused | 通知操作系统虚拟内存对应的物理内存已经不再需要,可以重用物理内存; |
runtime.sysFault | 将内存区域转换成保留状态,主要用于运行时的调试; |
runtime.sysMap | 保证内存区域可以快速转换至就绪状态; |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbqB2h7c-1652891154059)(https://img.draveness.me/2020-02-29-15829868066474-memory-regions-states-and-transitions.png)]
内存对齐
内存地址是所存储大小(子节)的整数倍,以便cpu可以一次将数据从内存中读取出来。
优点
- 提高可移植性,编译时将内存进行对齐。消除CPU的差异性,达到代码可移植。
- 提高内存的访问效率。32位CPU一次读取4个字节,64位CPU一次读取8个字节,这个长度称为CPU字长,将内存变为CPU字长整数倍,减少CPU碎片化读取,提高访问效率。
缺点
- 浪费内存空间,换取时间
结构体
- 结构体变量中成员的偏移量必须是成员大小的整数倍
- 整个结构体的地址必须是最大字节的整数倍(1/4/8/16…)
内存逃逸
每个函数都有自己的内存区域存放入参,局部变量,返回值等,存放于运行栈中栈帧中,函数运行后自动销毁。
函数结束后,如果还需要这些变量将会导致栈中数据分配到堆中,这种现象叫内存逃逸
逃逸机制
- 函数外部没有引用,优先放栈
- 函数外部存在引用,必定放堆
- 栈放不下,必定放堆
逃逸分析
go build -gcflags=-m main.go
- 指针逃逸
package main
func escape1() *int{
var a int = 1
return &a
}
func main(){
escape1()
}
// ./main.go 4:6 moved to heap a
- 栈空间不足
package main
func escape2() {
s :=make([]int,0,100000)
for index,_:=range s{
s[index]=index
}
}
func main(){
escape2()
}
// ./main.go 4:1 make([]int,1000,1000) escapes to heap
- 变量大小不缺定
package main
func escape3() *int{
number:=10
s :=make([]int,number)
for index:=0;index<len(s);index++{
s[index]=index
}
}
func main(){
escape3()
}
//./main.go:18:6: can inline escape3
//./main.go:15:9: inlining call to escape3
//./main.go:15:9: make([]int, number) escapes to heap
//./main.go:20:10: make([]int, number) escapes to heap
- 动态类型
package main
func escape4() {
fmt.println(1111)
}
func main(){
escape4()
}
//./main.go:33:6: can inline escape4
./main.go:34:13: inlining call to fmt.Println
//./main.go:6:6: can inline main
//./main.go:10:9: inlining call to escape4
//./main.go:10:9: inlining call to fmt.Println
//./main.go:10:9: 1111 escapes to heap
//./main.go:10:9: []interface {}{...} does not escape
//./main.go:34:14: 1111 escapes to heap
//./main.go:34:13: []interface {}{...} does not escape
//<autogenerated>:1: .this does not escape
- 闭包引用
package main
func escape5() func()int {
var i int =1
return func() int {
i++
return
}
}
func main(){
escape5()
}
//./main.go:36:9: can inline escape5.func1
//./main.go:4:6: can inline main
//./main.go:35:6: moved to heap: i
//./main.go:36:9: func literal escapes to heap
总结
- 栈上分配比堆内存分配效率更高,且不需要gc
- 逃逸分析目的是决定内存地址是栈还是堆,来分析gc存在性能瓶颈,编译时就可以知道
- 只要是指针变量都会在堆上分配,对于小变量使用值传递效率更高
go 内存管理
虚拟内存布局
go1.10版本以及以前使用连续内存
-
spans
- 存储内存管理单元mspan指针,每个指针对于1orN个page
- 512MB = 512GB/8KB(页大小)*8B(指针大小)
-
bitmap
-
每个字节都会表示
arena
区域中的 32 字节是否空闲 -
主要用于gc
-
16G = 512GB/32B*1B
-
-
arena
- 真正的堆区
- 512GB,page=8KB
g1.11版本以及之后引入二维稀疏内存
[l1][l2]heapArena
- Linux x86-64架构 l1=1 l2=4194304 32MB = 4194304*8B(指针大小) ,每个heapArena可以管理64MB内存
- 4M*64MB = 256TB内存
// runtime.heapArena
type heapArena struct {
bitmap [heapArenaBitmapBytes]byte // 标记内内存是否使用
spans [pagesPerArena]*mspan // 管理器mspan
pageInUse [pagesPerArena / 8]uint8
pageMarks [pagesPerArena / 8]uint8
pageSpecials [pagesPerArena / 8]uint8
checkmarks *checkmarksMap
zeroedBase uintptr //内存管理基地址
}
go内存管理设计
- 借鉴TCMalloc,采用多级缓冲
- mcache 线程内存分配对应结构
- mcentral 中心内存分配对应结构
- mheap 页堆内存分配对应结构
- mspan 内存管理单元结构体,对应class表
- 内存分配器采用线性/空闲列表结合
- 微对象使用mcache内的线性分配,若无则同小对象
- 小对象依次class表分级内存分配,最终使用空闲链表分配
- 大对象直接使用mheap内使用空闲链表分配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0nLBZ6R-1652891154061)(https://img.draveness.me/2020-02-29-15829868066479-go-memory-layout.png)]
mspan
- 内存管理单元,mcache,mheap均使用该结构记录
- 链表结构
- 只管理一类class,由class表
// runtime/mheap.go
// go:notinheap
type mspan struct {
next *mspan // mspan链表下一个指针
prev *mspan // mspan链表上一个指针
list *mSpanList // mspan链表头尾节点指针
// startAddr npages 可以计算出mspan管理的内存块范围
startAddr uintptr // 起始地址,所管理页的地址
freeindex uintptr // 扫描页中空闲对象初始索引
limit uintptr // 数据截止位置 申请大对象内存块使用
allocCache uint64 // allocBits的补码 用于快速查询空闲
allocBits *gcBits // 内存占用情况,对象位图
gcmarkBits *gcBits // gc标记情况,对象位图
allocCount uint16 // 已分配的对象数
nelems uintptr // 对象总数 对应class表中的 object
spanclass spanClass // classid 对应class表中的 class
elemsize uintptr // 对象大小 对应class表中的 bytes/obj
npages uintptr // span管理的页数*8KB对应class表中bytes/span
state mSpanStateBox // 内存管理状态
manualFreeList gclinkptr // 空闲对象列表
needzero uint8 //
sweepgen uint32 //扫描次数
// divMagic
divMul uint16
baseMask uint16
divShift uint8
divShift2 uint8
speciallock mutex
specials *special
}
mcache
- 微对象分配器(线性分配器)
- 小对象分配器,缓冲134类mspan作为线程内部使用
- 大对象分配器,直接使用mheap,其他分配依次mcache mcentral mheap
// runtime/mcache.go
// go:notinheap
type mcache struct {
// 微对象分配器
tiny uintptr //tiny对象基地址
tinyoffset uintptr //下一个空闲内存所在偏移量
tinyAllocs uintptr //tiny对象分配个数
// 小对象分配器
// 偶数位指针类型(需要gc扫描) 奇数位非指针类型
alloc [numSpanClasses]*mspan //68*2 按class分组的mspan列表
stackcache [_NumStackOrders]stackfreelist // 栈缓存
flushGen uint32
nextSample uintptr // trigger heap sample after allocating this many bytes
scanAlloc uintptr // bytes of scannable heap allocated
}
//初始化
//运行处理时P初始化时会调用此方法
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 {
// 所有mspan使用占位填充
c.alloc[i] = &emptymspan
}
c.nextSample = nextSample()
return c
}
//替换
func (c *mcache) refill(spc spanClass) {
s := c.alloc[spc]
// 其他代码
...
// 从中心缓存申请msapn
s = mheap_.central[spc].mcentral.cacheSpan()
if s == nil {
throw("out of memory")
}
if uintptr(s.allocCount) == s.nelems {
throw("span has no free space")
}
// 其他代码
...
c.alloc[spc] = s
}
mcentral
-
只管理一类class,由class表
-
两个包含/不包含空闲对象的mspan列表
- mspan列表多线程共享,使用互斥锁保护
// runtime/mcentral.go
type mcentral struct {
spanclass spanClass //classid
partial [2]spanSet // 空闲的span列表
full [2]spanSet // 非空闲的span列表
}
type spanSet struct {
spineLock mutex // 多线程访问加锁
spine unsafe.Pointer // 指向[]span的指针
spineLen uintptr // 长度
spineCap uintptr // 容量
index headTailIndex // 前32位时头指针 后32位时尾指针
}
// 初始化
func (c *mcentral) init(spc spanClass) {
c.spanclass = spc // 初始化spanclass
lockInit(&c.partial[0].spineLock, lockRankSpanSetSpine) // 锁
lockInit(&c.partial[1].spineLock, lockRankSpanSetSpine) // 锁
lockInit(&c.full[0].spineLock, lockRankSpanSetSpine) // 锁
lockInit(&c.full[1].spineLock, lockRankSpanSetSpine) // 锁
}
// 管理 mecache 获取mspan
func (c *mcentral) cacheSpan() *mspan {
// 其他代码
...
var s *mspan
// 1.尝试从清理过/包含空闲空间的结构中查找
if s = c.partialSwept(sg).pop(); s != nil {
goto havespan
}
// 2.尝试从未清理过/包含空闲空间的结构中查找
for ; spanBudget >= 0; spanBudget-- {
s = c.partialUnswept(sg).pop()
if s == nil {
break
}
if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
//清理
s.sweep(true)
goto havespan
}
}
// 3.尝试从未被清理过/不包含空闲空间的结构中查找
for ; spanBudget >= 0; spanBudget-- {
s = c.fullUnswept(sg).pop()
if s == nil {
break
}
if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
// 清理
s.sweep(true)
// 检查是否有空闲
freeIndex := s.nextFreeIndex()
if freeIndex != s.nelems {
s.freeindex = freeIndex
goto havespan
}
//
c.fullSwept(sg).push(s)
}
}
// 其他代码
...
//4. mcentral向mheap申请扩容
s = c.grow()
if s == nil {
return nil
}
havespan:
// 其他代码
...
n := int(s.nelems) - int(s.allocCount)
if n == 0 || s.freeindex == s.nelems || uintptr(s.allocCount) == s.nelems {
throw("span has no free objects")
}
freeByteBase := s.freeindex &^ (64 - 1)
whichByte := freeByteBase / 8
s.refillAllocCache(whichByte)
s.allocCache >>= s.freeindex % 64
return s
}
// 扩容
func (c *mcentral) grow() *mspan {
npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])
size := uintptr(class_to_size[c.spanclass.sizeclass()])
s := mheap_.alloc(npages, c.spanclass, true)
if s == nil {
return nil
}
// Use division by multiplication and shifts to quickly compute:
// n := (npages << _PageShift) / size
n := (npages << _PageShift) >> s.divShift * uintptr(s.divMul) >> s.divShift2
s.limit = s.base() + size*n
heapBitsForAddr(s.base()).initSpan(s) // 清理堆上位图
return s
}
mheap
//go:notinheap
type mheap struct {
lock mutex //多线程锁
pages pageAlloc // 页分配器
allspans []*mspan // 所有mspan 由锁保护
// heapArena二维希数内存
arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
//管理68跨度的mcentral
central [numSpanClasses]struct {
mcentral mcentral
pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
}
//其他字段
...
}
// runtime.heapArena 管理64MB
type heapArena struct {
bitmap [heapArenaBitmapBytes]byte // 标记内内存是否使用
spans [pagesPerArena]*mspan // 管理器mspan
pageInUse [pagesPerArena / 8]uint8
pageMarks [pagesPerArena / 8]uint8
pageSpecials [pagesPerArena / 8]uint8
checkmarks *checkmarksMap
zeroedBase uintptr //内存管理基地址
}
// 初始化
func (h *mheap) init() {
lockInit(&h.lock, lockRankMheap)
lockInit(&h.speciallock, lockRankMheapSpecial)
h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys)
h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys)
h.spanalloc.zero = false
// 初始化mcentral
for i := range h.central {
h.central[i].mcentral.init(spanClass(i))
}
h.pages.init(&h.lock, &memstats.gcMiscSys)
}
// 管理
// 从系统栈获取新的mspan
func (h *mheap) alloc(npages uintptr, spanclass spanClass, needzero bool) *mspan {
var s *mspan
systemstack(func() {
if h.sweepdone == 0 {
// 回收部分内存
h.reclaim(npages)
}
// 分配新的管理单元
s = h.allocSpan(npages, spanAllocHeap, spanclass)
})
// 其他代码
...
return s
}
//
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
// Function-global state.
gp := getg()
base, scav := uintptr(0), uintptr(0)
// 其他代码
...
pp := gp.m.p.ptr()
if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
c := &pp.pcache
// 如果处理器的页缓冲为空
if c.empty() {
lock(&h.lock)
// 全局页分配器放置到p的页缓冲
*c = h.pages.allocToCache()
unlock(&h.lock)
}
// 处理器页缓冲申请内存
base, scav = c.alloc(npages)
if base != 0 {
s = h.tryAllocMSpan()
if s != nil {
goto HaveSpan
}
}
}
lock(&h.lock)
// 其他代码
...
if base == 0 {
// 全局分配器分配页
base, scav = h.pages.alloc(npages)
if base == 0 {
// 扩容失败返回 nil
if !h.grow(npages) {
unlock(&h.lock)
return nil
}
// 重新获取
base, scav = h.pages.alloc(npages)
// 再次失败抛异常
if base == 0 {
throw("grew heap, but no adequate free space found")
}
}
}
if s == nil {
s = h.allocMSpanLocked()
}
// 其他代码
...
unlock(&h.lock)
HaveSpan:
// At this point, both s != nil and base != 0, and the heap
// lock is no longer held. Initialize the span.
s.init(base, npages)
if h.allocNeedsZero(base, npages) {
s.needzero = 1
}
nbytes := npages * pageSize
if typ.manual() {
s.manualFreeList = 0
s.nelems = 0
s.limit = s.base() + s.npages*pageSize
s.state.set(mSpanManual)
} else {
// We must set span properties before the span is published anywhere
// since we're not holding the heap lock.
s.spanclass = spanclass
if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
s.elemsize = nbytes
s.nelems = 1
s.divShift = 0
s.divMul = 0
s.divShift2 = 0
s.baseMask = 0
} else {
s.elemsize = uintptr(class_to_size[sizeclass])
s.nelems = nbytes / s.elemsize
m := &class_to_divmagic[sizeclass]
s.divShift = m.shift
s.divMul = m.mul
s.divShift2 = m.shift2
s.baseMask = m.baseMask
}
// Initialize mark and allocation structures.
s.freeindex = 0
s.allocCache = ^uint64(0) // all 1s indicating all free.
s.gcmarkBits = newMarkBits(s.nelems)
s.allocBits = newAllocBits(s.nelems)
// 其他代码
...
h.setSpans(s.base(), npages, s)
// 其他代码
...
return s
}
// 扩容
func (h *mheap) grow(npage uintptr) bool {
assertLockHeld(&h.lock)
// 计算需要的页总内存
ask := alignUp(npage, pallocChunkPages) * pageSize
totalGrowth := uintptr(0)
end := h.curArena.base + ask
nBase := alignUp(end, physPageSize)
if nBase > h.curArena.end || /* overflow */ end < h.curArena.base {
//arena区域不够需要扩容
av, asize := h.sysAlloc(ask)
if av == nil {
print("runtime: out of memory: cannot allocate ", ask, "-byte block (", memstats.heap_sys, " in use)\n")
return false
}
if uintptr(av) == h.curArena.end {
//老区扩容
h.curArena.end = uintptr(av) + asize
} else {
//新区域
if size := h.curArena.end - h.curArena.base; size != 0 {
h.pages.grow(h.curArena.base, size)
totalGrowth += size
}
// Switch to the new space.
h.curArena.base = uintptr(av)
h.curArena.end = uintptr(av) + asize
}
atomic.Xadd64(&memstats.heap_released, int64(asize))
stats := memstats.heapStats.acquire()
atomic.Xaddint64(&stats.released, int64(asize))
memstats.heapStats.release()
nBase = alignUp(h.curArena.base+ask, physPageSize)
}
// 更新arena
v := h.curArena.base
h.curArena.base = nBase
// 更新页分配器
h.pages.grow(v, nBase-v)
totalGrowth += nBase - v
// 回收空闲内存页
if retained := heapRetained(); retained+uint64(totalGrowth) > h.scavengeGoal {
todo := totalGrowth
if overage := uintptr(retained + uint64(totalGrowth) - h.scavengeGoal); todo > overage {
todo = overage
}
h.pages.scavenge(todo, false)
}
return true
}
内存分配
// new 操作被编译器翻译调用此方法
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
//
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// 其他代码
...
mp := acquirem() // 放置M被GC抢占
// 其他代码
...
mp.mallocing = 1
shouldhelpgc := false
// 1. 计算内存的规格
dataSize := size
// 2. mcache找到合适规格的块内存
c := getMCache() // 获取当前mcache
// 其他代码
...
var span *mspan
var x unsafe.Pointer
noscan := typ == nil || typ.ptrdata == 0
if size <= maxSmallSize {
if noscan && size < maxTinySize {
// 2.1微对象分配
// 1.先使用微型分配器
// 2.使用mcache/mcentral/heaparean
off := c.tinyoffset
// 进行内存对齐
if size&7 == 0 {
off = alignUp(off, 8)
} else if sys.PtrSize == 4 && size == 12 {
off = alignUp(off, 8)
} else if size&3 == 0 {
off = alignUp(off, 4)
} else if size&1 == 0 {
off = alignUp(off, 2)
}
//分配
if off+size <= maxTinySize && c.tiny != 0 {
x = unsafe.Pointer(c.tiny + off)
c.tinyoffset = off + size
c.tinyAllocs++
mp.mallocing = 0
releasem(mp)
return x
}
//没有则 通过span分配
span = c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
// 返回新内存
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
if size < c.tinyoffset || c.tiny == 0 {
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
} else {
// 2.2小对象分配
// 使用mcache/mcentral/heaparean
var sizeclass uint8
if size <= smallSizeMax-8 {
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
size = uintptr(class_to_size[sizeclass])
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span) // 检查管理是否有空闲空间
if v == 0 {
//重新mcache/mcentral/heaparean 获取管理单元
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
if needzero && span.needzero != 0 {
memclrNoHeapPointers(unsafe.Pointer(v), size)
}
}
} else {
// 2.3大对象分配
// 直接在对上分配
shouldhelpgc = true
// mheap 申请一个classid = 0 的管理单元
span = c.allocLarge(size, needzero, noscan)
span.freeindex = 1
span.allocCount = 1
x = unsafe.Pointer(span.base())
size = span.elemsize
}
// 其他代码
...
// 内存屏障
publicationBarrier()
// 其他代码
...
mp.mallocing = 0
releasem(mp)
// 其他代码
...
if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x
}