Heap管理
内置运行时的编程语言通常会抛弃传统的内存分配方式,改由自主管理内存。这样可以完成类 似预分配、内存池等操作,以避开频繁地申请释放内存引起的系统调而导致的性能问题。当然,还有一个重要原因是 为了更好地配合语言的垃圾回收机制。
Go内部实现也不例外,go runtime接管了所有的内存申请和释放动作。在os上层实现了内存池机制(源自tcmalloc设计)。
Go内存池管理的核心数据结构为mHeap。该结构管理从os申请的大块内存,将大块内存切分成多种不同大小的小块,每种小块由数据结构mspan表示。mheap通过数组+链表的方式来维护所有的空闲span。
应用程序在申请内存时一般都是以object为单位。在go runtime内部必须要计算object大小,然后找到合适的mspan大小。从这里面分配出想要的内存,返回给应用程序。
程序在向go runtime申请分配某种object所需内存时,会计算出object占用的内存空间,然后找到最接近的mspan(因为mspan管理的块大小是按照固定倍数增长的方案。如一个17字节的object需要的块大小应该是24字节,存在轻微的内存浪费),将其分配出去。
对象分配
// implementation of new builtin
func newobject(typ *_type) unsafe.Pointer {
flags := uint32(0)
if typ.kind&kindNoPointers != 0 {
flags |= flagNoScan
}
return mallocgc(uintptr(typ.size), typ, flags)
}
// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
// Set mp.mallocing to keep from being preempted by 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
c := gomcache()
var s *mspan
var x unsafe.Pointer
if size <= maxSmallSize {
// 对极小对象(<=16B)分配的优化
if flags&flagNoScan != 0 && size < maxTinySize {
off := c.tinyoffset
// Align tiny pointer for required (conservative) alignment.
if size&7 == 0 {
off = round(off, 8)
} else if size&3 == 0 {
off = round(off, 4)
} else if size&1 == 0 {
off = round(off, 2)
}
// 将众多小对象存储在同一个block内
if off+size <= maxTinySize && c.tiny != nil {
x = add(c.tiny, off)
c.tinyoffset = off + size
c.local_tinyallocs++
mp.mallocing = 0
releasem(mp)
return x
}
// 存储小对象的块为nil或者之前的小对象块已经容纳不下了,申请一个新的小对象块并
// Allocate a new maxTinySize block.
s = c.alloc[tinySizeClass]
v := s.freelist
if v.ptr() == nil {
systemstack(func() {
mCache_Refill(c, tinySizeClass)
})
shouldhelpgc = true
s = c.alloc[tinySizeClass]
v = s.freelist
}
s.freelist = v.ptr().next
s.ref++
prefetchnta(uintptr(v.ptr().next))
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// See if we need to replace the existing tiny block with the new one
// based on amount of remaining free space.
if size < c.tinyoffset {
c.tiny = x
c.tinyoffset = size
}
}