内存分配函数
[TOC]
mallocgc() 内存主入口
// 分配特定字节大小的对象.
// 小对象 直接优先从线程缓存 mcache 的 free list 中分配
// 大对象 (> 32 kB) 会直接从页堆 mheap 中分配内存.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if gcphase == _GCmarktermination { // _GCmarktermination 阶段不能进行内存分配
throw("mallocgc called with gcphase == _GCmarktermination")
}
if size == 0 { // 所有大小为 0 的对象都使用同一块内存地址
return unsafe.Pointer(&zerobase)
}
// debug.sbrk ...
// 辅助 gorotuine,主要是在 GC 阶段,负责内存分配
// 如果没有在执行 GC,那么这个 g 可以是 nil
var assistG *g //
if gcBlackenEnabled != 0 {
// 当前的用户 G 负责这次分配
assistG = getg()
if assistG.m.curg != nil {
assistG = assistG.m.curg
}
// Charge the allocation against the G. We'll account
// for internal fragmentation at the end of mallocgc.
assistG.gcAssistBytes -= int64(size)
if assistG.gcAssistBytes < 0 {
// This G is in debt. Assist the GC to correct
// this before allocating. This must happen
// before disabling preemption.
gcAssistAlloc(assistG)
}
}
// 设置 mp.mallocing 状态,以使其避免被 GC 抢占
mp := acquirem()
if mp.mallocing != 0 {
throw("malloc deadlock")
}
if mp.gsignal == getg() {
throw("malloc during signal")
}
mp.mallocing = 1
shouldhelpgc := false
dataSize := size
var c *mcache
if mp.p != 0 {
c = mp.p.ptr().mcache
} else {
// We will be called without a P while bootstrapping,
// in which case we use mcache0, which is set in mallocinit.
// mcache0 is cleared when bootstrapping is complete,
// by procresize.
c = mcache0
if c == nil {
throw("malloc called with no P")
}
}
var span *mspan
var x unsafe.Pointer
noscan := typ == nil || typ.ptrdata == 0
if size <= maxSmallSize {
if noscan && size < maxTinySize { // 小于 16 字节的微且没有指针的对象内存分配
// Tiny allocator.
//
// Tiny allocator 会将几个 tiny 的内存分配请求组合为单个内存块。
// 只有当该内存块中所有子对象都变为待回收后,才会释放这部分内存块。
// 子对象必须是 noscan 类型(不包含指针),这样可以确保浪费的内存是可控的。
//
// 用来组合小对象的内存块的大小是可调(通过maxTinySize)。
// 当前的设置是 16 bytes,最坏情况下会浪费 2x 内存(当所有子对象中只有一个对象是可达状态时)。
// 如果修改为 8 bytes ,虽然完全没有浪费,但是却不太可能进行组合操作。
// 32 bytes 能够提高合并的可能性,但是最坏情况下会造成 4x 浪费。
// 不考虑 block 大小的话,最好的情况下是 8x。
//
// 从 tiny allocator 中获得的内存不能被显式释放。
// 因此当一个对象需要被显式释放时,我们要确保它的 size >= maxTinySize
//
// 当对象都是由 tiny allocator 分配时,SetFinalizer 有一种特殊情况,
// 这种情况下会允许在一个内存块中设置内部字节的 finalizers
//
// 实现 tiny allocator 的主要目标是对那些小字符串和独立的逃逸变量。
// 在一个 json benchmark 中,这个 allocator 会将其性能提升 ~12%
// 并且使堆大小降低了 ~20%。
off := c.tinyoffset
// 对齐,调整偏移量
if size&7 == 0 {
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 {
// 把将tiny对象适配到已存在的 tiny 块
x = unsafe.Pointer(c.tiny + off)
c.tinyoffset = off + size
c.local_tinyallocs++
mp.mallocing = 0
releasem(mp)
return x
}
// 分配一个新的 maxTinySize 大小的块, 放入 mcache.alloc 数组中
span = c.alloc[tinySpanClass] // 即spanClass == 2 ,16字节的
// nextFreeFast 从 mspam 返回下一个空闲对象,如果有的话。否则返回0。
v := nextFreeFast(span)
if v == 0 {
// 若 mspan 没有空闲内存,则从中心缓存或页堆获取新的 mspan 内存对象
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// 根据剩余的空闲空间,来看看我们是否需要将已有的 tiny 块替换为新块
if size < c.tinyoffset || c.tiny == 0 {
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
} else {
// 16B到32KB的小对象以及含有指针的微对象的内存分配
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) // 计算对象合适的 spanClass
span = c.alloc[spc] // 根据 spanClass 取对应的 mspan
v := nextFreeFast(span)
if v == 0 {
//从中心缓存或页堆获取新的 mspan 内存对象
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
if needzero && span.needzero != 0 {
// 用 memclrNoHeapPointers 进行零初始化
memclrNoHeapPointers(unsafe.Pointer(v), size)
}
}
} else {
// 大于 32KB 的大对象内存分配
shouldhelpgc = true
systemstack(func() {
span = largeAlloc(size, needzero, noscan) // 直接从 mheap 上分配 span
})
span.freeindex = 1
span.allocCount = 1
x = unsafe.Pointer(span.base())
size = span.elemsize
}
var scanSize uintptr
if !noscan {
// If allocating a defer+arg block, now that we've picked a malloc size
// large enough to hold everything, cut the "asked for" size down to
// just the defer header, so that the GC bitmap will record the arg block
// as containing nothing at all (as if it were unused space at the end of
// a malloc block caused by size rounding).
// The defer arg areas are scanned as part of scanstack.
if typ == deferType {
dataSize = unsafe.Sizeof(_defer{})
}
heapBitsSetType(uintptr(x), size, dataSize, typ)
if dataSize > typ.size {
// Array allocation. If there are any
// pointers, GC has to scan to the last
// element.
if typ.ptrdata != 0 {
scanSize = dataSize - typ.size + typ.ptrdata
}
} else {
scanSize = typ.ptrdata
}
c.local_scan += scanSize
}
// 确保上面初始化 x 并设置 heap 对应的 bits 之类的存储操作发生
// 在 caller 使 x 对垃圾收集器可见之前。否则的话,在那些弱有序
// 的平台上,垃圾收集器可能会跟踪一个指向 x 的指针,
// 但是看到的内存却是未初始化或者 heap bits 是过期的数据
publicationBarrier()
// GC 期间分配的黑色对象。所有持有 nil 对象的槽都无需扫描。
// 这里可能会和 GC 发生竞争,如果正在标记 bit 时,需要原子地来执行这个动作
if gcphase != _GCoff {
gcmarknewobject(span, uintptr(x), size, scanSize)
}
if raceenabled {
racemalloc(x, size)
}
if msanenabled {
msanmalloc(x, size)
}
mp.mallocing = 0
releasem(mp)
if debug.allocfreetrace != 0 {
tracealloc(x, size, typ)
}
if rate := MemProfileRate; rate > 0 {
if rate != 1 && size < c.next_sample {
c.next_sample -= size
} else {
mp := acquirem()
profilealloc(mp, x, size)
releasem(mp)
}
}
if assistG != nil {
// Account for internal fragmentation in the assist
// debt now that we know it.
assistG.gcAssistBytes -= int64(size - dataSize)
}
if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x
}
nextFreeFast() 线程缓存分配
nextFreeFast() 用于快速获取空闲的内存,没有则返回0
func nextFreeFast(s *mspan) gclinkptr {
theBit := sys.Ctz64(s.allocCache) // Is there a free object in the allocCache?
if theBit < 64 {
result := s.freeindex + uintptr(theBit)
if result < s.nelems {
freeidx := result + 1
if freeidx%64 == 0 && freeidx != s.nelems {
return 0
}
s.allocCache >>= uint(theBit + 1)
s.freeindex = freeidx // 标记下一个空对象索引
s.allocCount++
return gclinkptr(result*s.elemsize + s.base())
}
}
return 0
}
runtime.nextFreeFast
会利用内存管理单元中的 mspan.allocCache
字段,快速找到该字段中bitmao位是 1 的位数, 1 表示该位对应的内存空间是空闲的。
找到了空闲的对象后,就可以更新内存管理单元的 allocCache
、freeindex
等字段并返回该片内存地址;如果没有找到空闲的内存,运行时会通过 runtime.mcache.nextFree
找到新的内存管理单元。
nextFree() 中心缓存分配
如果我们在线程缓存中没有找到可用的内存管理单元,runtime.mcache.refill
使用中心缓存中的内存管理单元替换已经不存在可用对象的结构体,该方法会调用新结构体的 runtime.mspan.nextFreeIndex
获取空闲的内存并返回。
// nextFree 会从 cached span 中寻找下一个空闲对象
// 如果未找到,会用一个持有可用对象的 span 来填充 cache 并将该对象返回,同时携带一个 flag
// 该 flag 会标志此次分配是否是重量级的内存分配操作。如果是重量级分配,那么 caller 必须
// 确定是否需要启动一次新的 GC 循环,如果 GC 当前活跃的话,该 goroutine 可能需要协助进行 GC
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
s = c.alloc[spc]
shouldhelpgc = false
freeIndex := s.nextFreeIndex()
if freeIndex == s.nelems {
// The span is full.
if uintptr(s.allocCount) != s.nelems {
println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount != s.nelems && freeIndex == s.nelems")
}
c.refill(spc) // 从中心列表扩充新的 span
shouldhelpgc = true
s = c.alloc[spc]
freeIndex = s.nextFreeIndex()
}
if freeIndex >= s.nelems {
throw("freeIndex is not valid")
}
v = gclinkptr(freeIndex*s.elemsize + s.base())
s.allocCount++
if uintptr(s.allocCount) > s.nelems {
println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount > s.nelems")
}
return
}
mcache.refill()
mcache.refill
方法会为线程缓存 mcache
获取一个指定跨度类的内存管理单元 mspan
,被替换的单元不能包含空闲的内存空间,而获取的单元中需要至少包含一个空闲对象用于分配内存:
// refill acquires a new span of span class spc for c. This span will
// have at least one free object. The current span in c must be full.
//
// Must run in a non-preemptible context since otherwise the owner of
// c could change.
func (c *mcache) refill(spc spanClass) {
s := c.alloc[spc] // 把正在使用的 span 返还给中心列表 mcentral
if uintptr(s.allocCount) != s.nelems {
throw("refill of span with free space remaining")
}
if s != &emptymspan {
// 标记这个 span 不在被mcache使用
if s.sweepgen != mheap_.sweepgen+3 {
throw("bad sweepgen in refill")
}
if go115NewMCentralImpl {
mheap_.central[spc].mcentral.uncacheSpan(s)
} else {
atomic.Store(&s.sweepgen, mheap_.sweepgen)
}
}
// 从中心列表 mcentrail 申请一个特定spanclass新的 span
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")
}
// 表明此 span 已被缓存,并在下一个扫描阶段防止异步扫描。
s.sweepgen = mheap_.sweepgen + 3
c.alloc[spc] = s
}
sweepgen GC 扫描计数值,每次GC后会自增2, 每个等待处理的 span 也有此字段。
largeAlloc() 大对象mheap分配
运行时对于大于 32KB 的大对象会单独处理,我们不会从线程缓存或者中心缓存中获取内存管理单元,而是直接在系统的栈中调用 runtime.largeAlloc
函数分配大片的内存。该函数会计算分配该对象所需要的页数,它会按照页数为在堆上申请内存。申请内存时会创建一个跨度类为 0 的 runtime.spanClass
并调用 runtime.mheap.alloc
分配一个管理对应内存的管理单元。
func largeAlloc(size uintptr, needzero bool, noscan bool) *mspan {
// print("largeAlloc size=", size, "\n")
if size+_PageSize < size {
throw("out of memory")
}
// 计算所需页数,即 size 处以 page 大小 8KB
npages := size >> _PageShift
if size&_PageMask != 0 {
npages++
}
// Deduct credit for this span allocation and sweep if
// necessary. mHeap_Alloc will also sweep npages, so this only
// pays the debt down to npage pages.
deductSweepCredit(npages*_PageSize, npages)
spc := makeSpanClass(0, noscan) //计算 spanClass
s := mheap_.alloc(npages, spc, needzero) // 从mheap申请内存
if s == nil {
throw("out of memory")
}
if go115NewMCentralImpl {
// Put the large span in the mcentral swept list so that it's
// visible to the background sweeper.
mheap_.central[spc].mcentral.fullSwept(mheap_.sweepgen).push(s)
}
s.limit = s.base() + size
heapBitsForAddr(s.base()).initSpan(s)
return s
}
systemstack() 函数栈运行
runtime.systemstack
在系统栈上运行给定的函数。Go 文件中声明了函数签名。针对不同的cpu架构采用汇编语言实现。
// systemstack runs fn on a system stack.
// If systemstack is called from the per-OS-thread (g0) stack, or
// if systemstack is called from the signal handling (gsignal) stack,
// systemstack calls fn directly and returns.
// Otherwise, systemstack is being called from the limited stack
// of an ordinary goroutine. In this case, systemstack switches
// to the per-OS-thread stack, calls fn, and switches back.
// It is common to use a func literal as the argument, in order
// to share inputs and outputs with the code around the call
// to system stack:
//
// ... set up y ...
// systemstack(func() {
// x = bigcall(y)
// })
// ... use x ...
//
//go:noescape
func systemstack(fn func())
以 amd64 为例
// func systemstack(fn func())
TEXT runtime·systemstack(SB), NOSPLIT, $0-8
MOVQ fn+0(FP), DI // DI = fn
get_tls(CX)
MOVQ g(CX), AX // AX = g
MOVQ g_m(AX), BX // BX = m
CMPQ AX, m_gsignal(BX)
JEQ noswitch
MOVQ m_g0(BX), DX // DX = g0
CMPQ AX, DX
JEQ noswitch
CMPQ AX, m_curg(BX)
JNE bad
// switch stacks
// save our state in g->sched. Pretend to
// be systemstack_switch if the G stack is scanned.
MOVQ $runtime·systemstack_switch(SB), SI
MOVQ SI, (g_sched+gobuf_pc)(AX)
MOVQ SP, (g_sched+gobuf_sp)(AX)
MOVQ AX, (g_sched+gobuf_g)(AX)
MOVQ BP, (g_sched+gobuf_bp)(AX)
// switch to g0
MOVQ DX, g(CX)
MOVQ (g_sched+gobuf_sp)(DX), BX
// make it look like mstart called systemstack on g0, to stop traceback
SUBQ $8, BX
MOVQ $runtime·mstart(SB), DX
MOVQ DX, 0(BX)
MOVQ BX, SP
// call target function
MOVQ DI, DX
MOVQ 0(DI), DI
CALL DI
// switch back to g
get_tls(CX)
MOVQ g(CX), AX
MOVQ g_m(AX), BX
MOVQ m_curg(BX), AX
MOVQ AX, g(CX)
MOVQ (g_sched+gobuf_sp)(AX), SP
MOVQ $0, (g_sched+gobuf_sp)(AX)
RET
noswitch:
// already on m stack; tail call the function
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVQ DI, DX
MOVQ 0(DI), DI
JMP DI
bad:
// Bad: g is not gsignal, not g0, not curg. What is it?
MOVQ $runtime·badsystemstack(SB), AX
CALL AX
INT $3
fixalloc 固定分配器
从 runtime 的角度,整个进程内的对象可分为两类:一种,自然是从 arena 区域分配的用户对象;另一种,则是运行时自身运行和管理所需的管理对象,比如管理 arena 内存片段的 span,提供无锁分配的 mcache等等。
管理对象的生命周期并不像用户对象那样复杂,且类型和长度都相对固定,所以算法策略显然不用那么复杂。还有,它们相对较长的生命周期也不适合占用 arena 区域,否则会导致更多碎片。为此,运行时专门设计了 fixalloc
固定分配器来为管理对象分配内。
fixAlloc是一个简单的用于固定大小对象的 free-list
分配器。fixAlloc
底层会调用 sysAlloc
方法分配内存, malloc 使用包裹在 sysAlloc 周围的 fixAlloc 来管理mcache和mspan等内存管理 off-heap
对象,如:
- mheap: 全局唯一的,基于页(8K)管理堆内存.
- mspan: mheap 中的页组成,内存管理单元.
- mcentral: 按特定的 spanClass 管理 span.
- mcache: 每个 P 的线程mspan缓存.
- mstats: 内存分配数据统计.
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 // 已使用的字节数
stat *uint64
zero bool // zero allocations
}
//go:notinheap
type mlink struct {
next *mlink
}
fixalloc.init():按给定的大小初始化分配器
func (f *fixalloc) init(size uintptr, first func(arg, p unsafe.Pointer), arg unsafe.Pointer, stat *uint64)
fixalloc.alloc():分配内存
func (f *fixalloc) alloc() unsafe.Pointer {
if f.size == 0 {
print("runtime: use of FixAlloc_Alloc before FixAlloc_Init\n")
throw("runtime: internal error")
}
// 优先从复用链表获取内存
if f.list != nil {
v := unsafe.Pointer(f.list)
f.list = f.list.next
f.inuse += f.size
if f.zero {
memclrNoHeapPointers(v, f.size)
}
return v
}
// 如果剩余内存块已不足分配,则获取新的16KB内存块, _FixAllocChunk == 16 << 10 字节
if uintptr(f.nchunk) < f.size {
// persistentalloc 函数会调用 sysAlloc
f.chunk = uintptr(persistentalloc(_FixAllocChunk, 0, f.stat))
f.nchunk = _FixAllocChunk
}
//获取新内存块时执行关联函数(通常用作初始化和贝数据)
v := unsafe.Pointer(f.chunk)
if f.first != nil {
f.first(f.arg, v)
}
// 更新属性
f.chunk = f.chunk + f.size
f.nchunk -= uint32(f.size)
f.inuse += f.size
return v
}
fixalloc.free()释放内存,即放回服用链表即可
func (f *fixalloc) free(p unsafe.Pointer) {
f.inuse -= f.size
v := (*mlink)(p)
v.next = f.list
f.list = v
}
当运行时在初始化mheap
时,根据不同的大小,一共构建了5 种固定分配器。
type mheap struct {
...
spanalloc fixalloc // allocator for span*
cachealloc fixalloc // allocator for mcache*
specialfinalizeralloc fixalloc // allocator for specialfinalizer*
specialprofilealloc fixalloc // allocator for specialprofile*
arenaHintAlloc fixalloc // allocator for arenaHints
}
// Initialize the heap.
func (h *mheap) init() {
...
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)
...
}
固定分配器持有的这个16KB内存块通过函数persistentalloc
分自 persistent 区域。该区域在很多地方为运行时提供后备内存,目的同样是为了减少并发锁,减少内存申请系统调用。
persistentalloc
type persistentAlloc struct {
base *notInHeap
off uintptr
}
var globalAlloc struct {
mutex
persistentAlloc
}
func persistentalloc(size, align uintptr, sysStat *uint64) unsafe.Pointer {
var p *notInHeap
systemstack(func() {
p = persistentalloc1(size, align, sysStat)
})
return unsafe.Pointer(p)
}
// Must run on system stack because stack growth can (re)invoke it.
// See issue 9174.
//go:systemstack
func persistentalloc1(size, align uintptr, sysStat *uint64) *notInHeap {
const (
maxBlock = 64 << 10 // VM reservation granularity is 64K on windows
)
if size == 0 {
throw("persistentalloc: size == 0")
}
if align != 0 {
if align&(align-1) != 0 {
throw("persistentalloc: align is not a power of 2")
}
if align > _PageSize {
throw("persistentalloc: align is too large")
}
} else {
align = 8
}
// 如若大雨 分配大于64KB的内存块
if size >= maxBlock {
return (*notInHeap)(sysAlloc(size, sysStat))
}
mp := acquirem()
//后备内存块存放位置(本地或全局)
var persistent *persistentAlloc
if mp != nil && mp.p != 0 {
persistent = &mp.p.ptr().palloc
} else {
lock(&globalAlloc.mutex)
persistent = &globalAlloc.persistentAlloc
}
//偏移位置对齐
persistent.off = alignUp(persistent.off, align)
//如果后备块空间不足,则重新申请
if persistent.off+size > persistentChunkSize || persistent.base == nil {
//申请新256KB后备内存
persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
if persistent.base == nil {
if persistent == &globalAlloc.persistentAlloc {
unlock(&globalAlloc.mutex)
}
throw("runtime: cannot allocate memory")
}
// Add the new chunk to the persistentChunks list.
for {
chunks := uintptr(unsafe.Pointer(persistentChunks))
*(*uintptr)(unsafe.Pointer(persistent.base)) = chunks
if atomic.Casuintptr((*uintptr)(unsafe.Pointer(&persistentChunks)), chunks, uintptr(unsafe.Pointer(persistent.base))) {
break
}
}
persistent.off = alignUp(sys.PtrSize, align)
}
p := persistent.base.add(persistent.off)
persistent.off += size
releasem(mp)
if persistent == &globalAlloc.persistentAlloc {
unlock(&globalAlloc.mutex)
}
if sysStat != &memstats.other_sys {
mSysStatInc(sysStat, size)
mSysStatDec(&memstats.other_sys, size)
}
return p
}
recordspan()
在 mheap 初始化阶段 mheap.init()
中,固定分配器函数h.spanalloc.init()
,初始化时,传入了关联函数 recordspan()
,其作用是按需扩张 mheap.allspans
的存储空间,mheap.allspans
是一个[]*mspan
数组,里面保存了所有 mspan
对象指针,供垃圾回收时遍历。
内存分配器 spans 区域虽然保存了 page/span映射关系,但有很多重复,基于效率考虑,并不适合用来作为遍历对象。
// The bootstrap sequence is:
//
// call osinit
// call schedinit
// make & queue new G
// call runtime·mstart
//
// The new G calls runtime·main.
func schedinit() {
...
mallocinit()
...
}
func mallocinit() {
...
mheap_.init()
...
}
// Initialize the heap.
func (h *mheap) init() {
...
h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
...
}
//go:nowritebarrierrec
func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
h := (*mheap)(vh)
s := (*mspan)(p)
// 如果 allspans 切片空间已满
if len(h.allspans) >= cap(h.allspans) {
// 计算新的容量
n := 64 * 1024 / sys.PtrSize
if n < cap(h.allspans)*3/2 {
n = cap(h.allspans) * 3 / 2
}
// 申请新的内存空间,直接用指针写切片内部属性。
var new []*mspan
sp := (*slice)(unsafe.Pointer(&new))
sp.array = sysAlloc(uintptr(n)*sys.PtrSize, &memstats.other_sys)
if sp.array == nil {
throw("runtime: cannot allocate memory")
}
sp.len = len(h.allspans)
sp.cap = n
//如果原空间有数据,复制数据新切片 new,然后释放
if len(h.allspans) > 0 {
copy(new, h.allspans)
}
oldAllspans := h.allspans
// h.allspans 指向新申请的空间
*(*notInHeapSlice)(unsafe.Pointer(&h.allspans)) = *(*notInHeapSlice)(unsafe.Pointer(&new))
// 释放旧空间
if len(oldAllspans) != 0 {
sysFree(unsafe.Pointer(&oldAllspans[0]), uintptr(cap(oldAllspans))*unsafe.Sizeof(oldAllspans[0]), &memstats.other_sys)
}
}
h.allspans = h.allspans[:len(h.allspans)+1]
h.allspans[len(h.allspans)-1] = s
}
上面的扩张直接用 mmap
在 arena
以外申请空间,即off-heap
内存,该部分空间不受GC管辖,因为此空间就是GC的一部分,其他应用申请内存对 ` h.allspans`的 append 操作引发的扩张是在 arena区域的。