操作系统内存管理

操作系统管理内存的存储单元是页(page),在 linux 中一般是 4KB。而且,操作系统还会使用 虚拟内存 来管理内存,在用户程序中,我们看到的内存是不是真实的内存,而是虚拟内存。当访问或者修改内存的时候,操作系统会将虚拟内存映射到真实的内存中。申请内存的组件是 Page Table 和 MMU(Memory Management Unit)。因为这个性能很重要,所以在 CPU 中专门有一个 TLB(Translation Lookaside Buffer)来缓存 Page Table 的内容。

为什么要用虚拟内存?

  1. 保护内存,每个进程都有自己的虚拟内存,不会相互干扰。防止修改和访问别的进程的内存。
  2. 减少内存碎片,虚拟内存是连续的,而真实的内存是不连续的。
  3. 当内存不够时,可以把虚拟内存映射到硬盘上,这样就可以使用硬盘的空间来扩展内存。

如上图所示,如果直接使用真实的内存,想要连续的内存肯定是申请不到的,这就是内存碎片的问题。而使用虚拟内存,通过 Page 映射的方式,保证内存连续。

Go 内存管理单元

page

在 go 中,管理内存的存储单元也是页(Page), 每个页的大小是 8KB。Go 内存管理是由 runtime 来管理的,runtime 会维护一个内存池,用来分配和回收内存。这样可以避免频繁的系统调用申请内存,提高性能。

mspan

mspan 是 go 内存管理基本单元,一个 mspan 包含一个或者多个 page。go 中有多种 mspan,每种 mspan 给不同的内存大小使用。

class bytes/obj bytes/span objects tail waste max waste min align
1 8 8192 1024 0 87.50% 8
2 16 8192 512 0 43.75% 16
3 24 8192 341 8 29.24% 8
4 32 8192 256 0 21.88% 32
5 48 8192 170 32 31.52% 16
6 64 8192 128 0 23.44% 64
7 80 8192 102 32 19.07% 16
8 96 8192 85 32 15.95% 32
9 112 8192 73 16 13.56% 16
... ... ... ... ... ... ...
64 24576 24576 1 0 11.45% 8192
65 27264 81920 3 128 10.00% 128
66 28672 57344 2 0 4.91% 4096
67 32768 32768 1 0 12.50% 8192
  1. class 是 mspan 的类型,每种类型对应不同的内存大小。
  2. obj 是每个对象的大小。
  3. span 是 mspan 的大小。
  4. objects 是 mspan 中对象的个数。
  5. tail waste 是 mspan 中最后一个对象的浪费空间。(不能整除造成的)
  6. max waste 是 mspan 中最大的浪费空间。(比如第一个中 每个都使用 1 byte,那么就所有都浪费 7 byte,7 / 8 = 87.50%)
  7. min align 是 mspan 中对象的对齐大小。如果超过这个就会分配下一个 mspan。

数据结构

mspan

type mspan struct {
// 双向链表 下一个 mspan 和 上一个 mspan
next *mspan
prev *mspan
// debug 使用的
list *mSpanList // 起始地址和页数 当 class 太大 要多个页组成 mspan
startAddr uintptr
npages uintptr // 手动管理的空闲对象链表
manualFreeList gclinkptr // 下一个空闲对象的地址 如果小于它 就不用检索了 直接从这个地址开始 提高效率
freeindex uint16
// 对象的个数
nelems uint16
// GC 扫描使用的空闲索引
freeIndexForScan uint16 // bitmap 每个 bit 对应一个对象 标记是否使用
allocCache uint64 // ...
// span 的类型
spanclass spanClass // size class and noscan (uint8)
// ...
}

spanClass

type spanClass uint8

func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
} //go:nosplit
func (sc spanClass) sizeclass() int8 {
return int8(sc >> 1)
} //go:nosplit
func (sc spanClass) noscan() bool {
return sc&1 != 0
}

spanClass 是 unint8 类型,一共有 8 位,前 7 位是 sizeclass,也就是上边 table 中的内容,一共有 (67 + 1) * 2 种类型, +1 是 0 代表比 67 class 的内存还大。最后一位是 noscan,也就是表示这个对象中是否含有指针,用来给 GC 扫描加速用的(无指针对象就不用继续扫描了),所以要 * 2。

mspan 详解

如果所示

  • mspan 是一个双向链表,如果不够用了,在挂一个就行了。
  • startAddr 是 mspan 的起始地址,npages 是 page 数量。根据 startAddr + npages * 8KB 就可以得到 mspan 的结束地址。
  • allocCache 是一个 bitmap,每个 bit 对应一个对象,标记是否使用。使用了 ctz(count trailing zero)。
  • freeindex 是下一个空闲对象的地址,如果小于它,就不用检索了,直接从这个地址开始,提高效率。

mcache

mache 是每个 P (processor)的结构体中都有的,是用来缓存的,因为每个 P 同一时间只有一个 goroutine 在执行,所以 mcache 是不需要加锁的。这也是 mcache 的设计初衷,减少锁的竞争,提高性能。

type p struct {
// ...
mcache *mcache
// ...
}
// 每个 P 的本队缓存
type mcache struct {
// 不在 gc 的堆中分配
_ sys.NotInHeap // The following members are accessed on every malloc,
// so they are grouped here for better caching.
nextSample uintptr // trigger heap sample after allocating this many bytes
scanAlloc uintptr // bytes of scannable heap allocated // 微对象分配器(<16B 不含指针)
tiny uintptr // 内存的其实地址
tinyoffset uintptr // 偏移量
tinyAllocs uintptr // 分配了多少个 tiny 对象 // span缓存数组,按大小类别索引
alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass // 用于不同大小的栈内存分配 go 的 堆上分配栈内存
stackcache [_NumStackOrders]stackfreelist // 控制 mcache 的刷新
flushGen atomic.Uint32
}

mcentral

mcentral 也是一种缓存,只不过在中心而不是在每个 P 上。mcentral 存在的意义也是减少锁竞争,如果没有 mcentral,那么只要从中心申请 mspan 就需要加锁。现在加上了 mcentral,申请时就需要加特别力度的锁就可以了,比如申请 class = 1 的 mspan 加 class = 1 的锁就可以了,不影响别人申请 class = 2 的 mspan。这样就可以较少锁竞争,提高性能。

type mcentral struct {
_ sys.NotInHeap
// mspan 的类别
spanclass spanClass // 部分使用的span列表
// 使用两个集合交替角色
// [0] -> 已清扫的spans
// [1] -> 未清扫的spans
partial [2]spanSet // list of spans with a free object
// 完全使用的 mspan
full [2]spanSet // list of spans with no free objects
} type spanSet struct {
// spanSet是一个两级数据结构,由一个可增长的主干(spine)指向固定大小的块组成。
// 访问spine不需要锁,但添加块或扩展spine时需要获取spine锁。
//
// 因为每个mspan至少覆盖8K的堆内存,且在spanSet中最多占用8字节,
// 所以spine的增长是相当有限的。 // 锁
spineLock mutex
// 原子指针,指向一个动态数组
spine atomicSpanSetSpinePointer // *[N]atomic.Pointer[spanSetBlock]
// 当前spine数组中实际使用的长度 原子类型
spineLen atomic.Uintptr // Spine array length
// spine数组的容量
spineCap uintptr // Spine array cap, accessed under spineLock // index是spanSet中的头尾指针,被压缩在一个字段中。
// head和tail都表示所有块的逻辑连接中的索引位置,其中head总是在tail之后或等于tail
// (等于tail时表示集合为空)。这个字段始终通过原子操作访问。
//
// head和tail各自的宽度为32位,这意味着在需要重置之前,我们最多支持2^32次push操作。
// 如果堆中的每个span都存储在这个集合中,且每个span都是最小尺寸(1个运行时页面,8 KiB),
// 那么大约需要32 TiB大小的堆才会导致无法表示的情况。
// 头部索引
index atomicHeadTailIndex
}
type mheap struct {
central [numSpanClasses]struct {
mcentral mcentral
// 填充字节 一般不能整除的时候 末尾的余数就不用了
pad [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
}
}

mheap

mheap 是全局的内存管理器,申请内存是 mcentral 不满足要求的时候,就会从 mheap 中申请,要加全局锁。如果 mheap 还不能满足,就会系统调用从操作系统申请,每次申请的最小单位是 Arena,也就是 64M。

type mheap struct {
_ sys.NotInHeap // 全局锁
lock mutex
// page 分配器 管理所有的page
pages pageAlloc sweepgen uint32 // sweep 代数 gc时候使用 // 所有的 mspan
allspans []*mspan // 正在使用的 page 数
pagesInUse atomic.Uintptr
// ...... // 用于定位内存地址是哪个 mspan 的
// 二维数组 1 << arenaL1Bits = 1 1 << arenaL2Bits = 4194304
arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena spanalloc fixalloc // span 分配器
cachealloc fixalloc // mcache 分配器
specialfinalizeralloc fixalloc // finalizer 分配器
// ......
}

heapArena


// A heapArena stores metadata for a heap arena. heapArenas are stored
// outside of the Go heap and accessed via the mheap_.arenas index.
type heapArena struct {
_ sys.NotInHeap // page 对应的 mspan
// pagesPerArena 8192 一个 page 8KB 所以一个 heapArena 可以存储 64M 的内存
spans [pagesPerArena]*mspan // 标记哪个 page 是在使用的
// /8 是 uint8 可以表示 8 个 page
pageInUse [pagesPerArena / 8]uint8 // 标记哪些span包含被标记的对象 用于 gc 加速
pageMarks [pagesPerArena / 8]uint8 // 标记哪些span包含特殊对象
pageSpecials [pagesPerArena / 8]uint8 checkmarks *checkmarksMap // arena中第一个未使用(已归零)页面的起始字节
zeroedBase uintptr
}

pageAlloc

分配 page 的结构体,是一个 radix tree 的结构,一共有 5 层,每一层都是一个 summary 数组,用于快速查找空闲页面。

type pageAlloc struct {
// 基数树 一共有 summaryLevels=5 层
// 基数树的摘要数组,用于快速查找空闲页面
summary [summaryLevels][]pallocSum // 二级页面位图结构
// 使用二级结构而不是一个大的扁平数组,是因为在64位平台上总大小可能非常大(O(GiB))
chunks [1 << pallocChunksL1Bits]*[1 << pallocChunksL2Bits]pallocData // 搜索起始地址
searchAddr offAddr // start 和 end 表示 pageAlloc 知道的块索引范围
start, end chunkIdx // ......
}
type pallocSum uint64

//  pallocSum 被划分成几个部分:
// 63位 62-42位 41-21位 20-0位
// [标志位] [end值] [max值] [start值]
// 1 21位 21位 21位 func (p pallocSum) start() uint {
// 检查第63位是否为1
if uint64(p)&uint64(1<<63) != 0 {
return maxPackedValue
}
// 否则,取最低21位
return uint(uint64(p) & (maxPackedValue - 1))
} func (p pallocSum) max() uint {
if uint64(p)&uint64(1<<63) != 0 {
return maxPackedValue
}
// 右移21位,然后取21位
return uint((uint64(p) >> logMaxPackedValue) & (maxPackedValue - 1))
} func (p pallocSum) end() uint {
if uint64(p)&uint64(1<<63) != 0 {
return maxPackedValue
}
// 右移42位,然后取21位
return uint((uint64(p) >> (2 * logMaxPackedValue)) & (maxPackedValue - 1))
}

内存分配流程

流程

go 中把 对象分成三类 tiny ,small 和 large。tiny 是小于 16B 的对象,small 是大于等于 16B 小于 32KB 的对象,large 是大于 32KB 的对象。tiny 分配器主要是为了减少内存碎片。

  1. 如果是 tiny object,直接使用 tiny 分配器分配。如果 tiny 分配器中的空间不够(定长位16B),就从 mchunk 中获取一个新的 16B 的对象作为 tiny 分配器的空间使用。
  2. 如果是 small object,根据所属的 class, 从 mcache 获取对应 mspan 中的内存。
  3. 如果 mspan 中的内存不够,根据所属的 class 从 mcentral 中获取新的 mspan ,从 mspan 中获取内存。(要 class 力度的锁)
  4. 如果 mcentral 中的 mspan 也不够,就从 mheap 中获取对应数量的 page 组装成 mspan,然后从新的 mspan 中获取内存。(全局锁)
  5. 如果 mheap 中的 mspan 也不够,就系统调用从操作系统获取新的 Arena。把内存 page 分配好,然后继续第四步。
  6. 如果是 large object,直接从第四部开始。

mallocgc

// 在 heap 上分配内存函数 size 对象大小 typ 对象类型 needzero 是否需要清零
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// gc 终止阶段不允许分配 这个是一个检查 正常情况下不会出现
if gcphase == _GCmarktermination {
throw("mallocgc called with gcphase == _GCmarktermination")
}
// 处理分类为 0 的情况
if size == 0 {
return unsafe.Pointer(&zerobase)
} // ...... // 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 := userSize
// 获取 M 和 M 所属 P 的 mcache
c := getMCache(mp)
if c == nil {
throw("mallocgc called without a P or outside bootstrapping")
}
var span *mspan
var header **_type
var x unsafe.Pointer
// 对象总是不是含有指针 如果不含有 就不用往下扫描了 用来 gc 加速
noscan := typ == nil || !typ.Pointers() // 是不是小对象 (< 32k - 8)
if size <= maxSmallSize-mallocHeaderSize {
// 如果对象大小小于 16B 且不含有指针 则使用 tiny 分配器
if noscan && size < maxTinySize {
off := c.tinyoffset
// 内存对齐一下
if size&7 == 0 {
off = alignUp(off, 8)
} else if goarch.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)
}
// 如果剩余空间足够 使用 tiny 分配器
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
}
// 重新 分配一个 tiny 使用
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 !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
} else {
// 处理小对象
// 处理对象头部 主要加入一些头部信息帮助 GC 加速
hasHeader := !noscan && !heapBitsInSpan(size)
if hasHeader {
size += mallocHeaderSize
}
// 根据不同的对象大小 使用不同的mspan
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]
// 使用缓存从 mspan 中获取空闲对象
v := nextFreeFast(span)
if v == 0 {
// 先从本地获取 span 如果本地没获取到 升级到 mcenral 获取
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
// 如果需要清零 处理一下
if needzero && span.needzero != 0 {
memclrNoHeapPointers(x, size)
}
// 设置头
if hasHeader {
header = (**_type)(x)
x = add(x, mallocHeaderSize)
size -= mallocHeaderSize
}
}
} else {
// 大对象分配 直接从 mheap 中获取 class = 0 的 mspan
shouldhelpgc = true
span = c.allocLarge(size, noscan)
span.freeindex = 1
span.allocCount = 1
size = span.elemsize
x = unsafe.Pointer(span.base())
if needzero && span.needzero != 0 {
delayedZeroing = true
}
if !noscan {
// Tell the GC not to look at this yet.
span.largeType = nil
header = &span.largeType
}
}
// ......
return x
}

nextFreeFast

func nextFreeFast(s *mspan) gclinkptr {
// 使用 ctz64 (amd64 中是 tzcnt 指令 ) 获取末尾的 0(以分配) 的个数 如果是 64 说明没有空闲对象
theBit := sys.TrailingZeros64(s.allocCache)
// 如果找到了空闲位置(theBit < 64)
if theBit < 64 {
result := s.freeindex + uint16(theBit)
if result < s.nelems {
freeidx := result + 1
if freeidx%64 == 0 && freeidx != s.nelems {
return 0
}
// 分配了 cache 移动一下
s.allocCache >>= uint(theBit + 1)
s.freeindex = freeidx
s.allocCount++
// result * elemsize:计算对象的偏移量
// base():获取span的起始地址
return gclinkptr(uintptr(result)*s.elemsize + s.base())
}
}
return 0
}

nextFree

// nextFree 从 mcache 中获取下一个空闲对象
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
s = c.alloc[spc]
shouldhelpgc = false
// 从 mcache 对象空位的偏移量
freeIndex := s.nextFreeIndex()
if freeIndex == s.nelems {
// mcache 没有靠你先对象 从 mcentral,mheap 获取
c.refill(spc)
shouldhelpgc = true
s = c.alloc[spc] freeIndex = s.nextFreeIndex()
} if freeIndex >= s.nelems {
throw("freeIndex is not valid")
} v = gclinkptr(uintptr(freeIndex)*s.elemsize + s.base())
s.allocCount++
if s.allocCount > s.nelems {
println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount > s.nelems")
}
return
}

一组一组获取空闲对象

func (s *mspan) nextFreeIndex() uint16 {
sfreeindex := s.freeindex
snelems := s.nelems
if sfreeindex == snelems {
return sfreeindex
}
if sfreeindex > snelems {
throw("s.freeindex > s.nelems")
} aCache := s.allocCache bitIndex := sys.TrailingZeros64(aCache)
for bitIndex == 64 {
// Move index to start of next cached bits.
sfreeindex = (sfreeindex + 64) &^ (64 - 1)
if sfreeindex >= snelems {
s.freeindex = snelems
return snelems
}
whichByte := sfreeindex / 8
// Refill s.allocCache with the next 64 alloc bits.
s.refillAllocCache(whichByte)
aCache = s.allocCache
bitIndex = sys.TrailingZeros64(aCache)
// nothing available in cached bits
// grab the next 8 bytes and try again.
}
result := sfreeindex + uint16(bitIndex)
if result >= snelems {
s.freeindex = snelems
return snelems
} s.allocCache >>= uint(bitIndex + 1)
sfreeindex = result + 1 if sfreeindex%64 == 0 && sfreeindex != snelems {
whichByte := sfreeindex / 8
s.refillAllocCache(whichByte)
}
s.freeindex = sfreeindex
return result
}
// 给 mcache 添加一个新的 mspan 一般是申请内存 mcache 中没有空闲对象了
func (c *mcache) refill(spc spanClass) {
s := c.alloc[spc] if s.allocCount != s.nelems {
throw("refill of span with free space remaining")
}
if s != &emptymspan {
// ......
// 如果不是空的 而且没有空闲对象 就把这个 span 放到 mcentral 中 mcache 使用不了这个 span 了
mheap_.central[spc].mcentral.uncacheSpan(s) // ......
} // 从 mcentral 获取新的 span 如果没有就从 mheap 再没有就系统调用申请内存
s = mheap_.central[spc].mcentral.cacheSpan() // ..... c.alloc[spc] = s
}

cacheSpan

func (c *mcentral) cacheSpan() *mspan {
// ...... // 尝试从以清扫的部分获取
sg := mheap_.sweepgen
if s = c.partialSwept(sg).pop(); s != nil {
goto havespan
} // 如果以清扫的没有 就马上开始主动清扫
sl = sweep.active.begin()
if sl.valid {
// 尝试从未清扫的部分使用的 span 列表中获取
for ; spanBudget >= 0; spanBudget-- {
s = c.partialUnswept(sg).pop()
if s == nil {
break
}
// 尝试获取 span
if s, ok := sl.tryAcquire(s); ok {
// 清扫它 并使用
s.sweep(true)
sweep.active.end(sl)
goto havespan
}
}
// 尝试从未清扫的已满 span 列表中获取
for ; spanBudget >= 0; spanBudget-- {
s = c.fullUnswept(sg).pop()
if s == nil {
break
}
if s, ok := sl.tryAcquire(s); ok {
s.sweep(true)
// 清扫之后 看有无可用的 没有就下一个
freeIndex := s.nextFreeIndex()
if freeIndex != s.nelems {
s.freeindex = freeIndex
sweep.active.end(sl)
goto havespan
}
c.fullSwept(sg).push(s.mspan)
}
}
sweep.active.end(sl)
}
trace = traceAcquire()
if trace.ok() {
trace.GCSweepDone()
traceDone = true
traceRelease(trace)
} // mcentral 中没有可用的 span 了 从 mheap 中获取
s = c.grow()
if s == nil {
return nil
} // 获取到了 span 了 上边会 goto 到这
havespan:
// ......
// 处理 allocCache 缓存
freeByteBase := s.freeindex &^ (64 - 1)
whichByte := freeByteBase / 8
s.refillAllocCache(whichByte)
s.allocCache >>= s.freeindex % 64 return s
}

grow

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)
if s == nil {
return nil
} // 计算这个 span 可以容纳多少个对象 和 偏移量等
n := s.divideByElemSize(npages << _PageShift)
s.limit = s.base() + size*n
s.initHeapBits(false)
return s
} func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan {
var s *mspan
systemstack(func() {
// 先清扫一些页 防止一直增长
if !isSweepDone() {
h.reclaim(npages)
}
s = h.allocSpan(npages, spanAllocHeap, spanclass)
})
return s
} func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
// 检查内存对其 ......
if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
// 尝试从缓存直接获取
*c = h.pages.allocToCache()
}
// 加锁
lock(&h.lock) if needPhysPageAlign {
// Overallocate by a physical page to allow for later alignment.
extraPages := physPageSize / pageSize // 尝试从 pageAlloc 获取页
base, _ = h.pages.find(npages + extraPages) } if base == 0 {
// 尝试分配所需页数
base, scav = h.pages.alloc(npages)
if base == 0 {
var ok bool
// 空间不足,尝试扩展
growth, ok = h.grow(npages)
if !ok {
unlock(&h.lock)
return nil
}
base, scav = h.pages.alloc(npages)
if base == 0 {
throw("grew heap, but no adequate free space found")
}
}
}
unlock(&h.lock) HaveSpan:
// ...... // 组装成 mspan
h.initSpan(s, typ, spanclass, base, npages) return s
}
// 向操作系统申请内存
func (h *mheap) grow(npage uintptr) (uintptr, bool) {
// 每次申请 4 M
ask := alignUp(npage, pallocChunkPages) * pageSize
// ......
av, asize := h.sysAlloc(ask, &h.arenaHints, true)
// ......
} // sysAlloc -> sysReserve -> sysReserveOS
func sysReserveOS(v unsafe.Pointer, n uintptr) unsafe.Pointer {
p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
return p
}
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) {
// ......
return sysMmap(addr, n, prot, flags, fd, off)
}

stack 内存

// newproc1
if newg == nil {
newg = malg(stackMin)
}
//newproc1 -> malg -> stackalloc
func stackalloc(n uint32) stack {
thisg := getg()
// ...... var v unsafe.Pointer
// 小栈 linux 下是 32k
if n < fixedStack<<_NumStackOrders && n < _StackCacheSize {
order := uint8(0)
n2 := n
for n2 > fixedStack {
order++
n2 >>= 1
}
var x gclinkptr
// 以下情况直接从全局池分配:
// 1. 禁用栈缓存
// 2. 没有关联的 P
// 3. 禁用抢占
if stackNoCache != 0 || thisg.m.p == 0 || thisg.m.preemptoff != "" {
lock(&stackpool[order].item.mu)
x = stackpoolalloc(order)
unlock(&stackpool[order].item.mu)
} else {
// 从 P 的本地缓存分配
c := thisg.m.p.ptr().mcache
x = c.stackcache[order].list
// 如果本地缓存为空,则重新填充
if x.ptr() == nil {
stackcacherefill(c, order)
x = c.stackcache[order].list
}
c.stackcache[order].list = x.ptr().next
c.stackcache[order].size -= uintptr(n)
}
v = unsafe.Pointer(x)
} else {
// 大栈
var s *mspan
npage := uintptr(n) >> _PageShift
log2npage := stacklog2(npage) // Try to get a stack from the large stack cache.
lock(&stackLarge.lock)
if !stackLarge.free[log2npage].isEmpty() {
s = stackLarge.free[log2npage].first
stackLarge.free[log2npage].remove(s)
}
unlock(&stackLarge.lock) lockWithRankMayAcquire(&mheap_.lock, lockRankMheap) if s == nil {
// 从堆中分配新的栈空间
s = mheap_.allocManual(npage, spanAllocStack)
if s == nil {
throw("out of memory")
}
osStackAlloc(s)
s.elemsize = uintptr(n)
}
v = unsafe.Pointer(s.base())
} // ...
return stack{uintptr(v), uintptr(v) + uintptr(n)}
}

stackpoolalloc stackpool

var stackpool [_NumStackOrders]struct {
item stackpoolItem
_ [(cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
} type stackpoolItem struct {
_ sys.NotInHeap
mu mutex
span mSpanList
} func stackpoolalloc(order uint8) gclinkptr {
list := &stackpool[order].item.span
s := list.first
if s == nil {
// 从 mheap 中申请 class = 0 对应页数的
s = mheap_.allocManual(_StackCacheSize>>_PageShift, spanAllocStack)
// ...
}
// 分配内存
x := s.manualFreeList
// ...
return x
}

stackcache

type mcache struct {
stackcache [_NumStackOrders]stackfreelist
} type stackfreelist struct {
list gclinkptr
size uintptr
} type gclinkptr uintptr func (p gclinkptr) ptr() *gclink {
return (*gclink)(unsafe.Pointer(p))
}

stackcacherefill

func stackcacherefill(c *mcache, order uint8) {
for size < _StackCacheSize/2 {
x := stackpoolalloc(order)
x.ptr().next = list
list = x
size += fixedStack << order
}
unlock(&stackpool[order].item.mu)
c.stackcache[order].list = list
c.stackcache[order].size = size
}

回收

//Goexit -> goexit1 -> goexit0 -> gdestroy
func gdestroy(gp *g) {
// ......
// 修改状态
casgstatus(gp, _Grunning, _Gdead)
// 把 gp 的变量制空 ....... // 把 m 上的 g 制空
dropg() gfput(pp, gp)
} func gfput(pp *p, gp *g) {
// ......
stksize := gp.stack.hi - gp.stack.lo // 如果栈不是默认大小 直接释放掉 只有默认大小才去复用
if stksize != uintptr(startingStackSize) {
// non-standard stack size - free it.
stackfree(gp.stack)
gp.stack.lo = 0
gp.stack.hi = 0
gp.stackguard0 = 0
} // 将 goroutine 放入空闲队列
pp.gFree.push(gp)
pp.gFree.n++
// 如果到达 64 个 goroutine 就把一部分放到全局队列中
if pp.gFree.n >= 64 {
var (
inc int32
stackQ gQueue
noStackQ gQueue
)
for pp.gFree.n >= 32 {
gp := pp.gFree.pop()
pp.gFree.n--
if gp.stack.lo == 0 {
noStackQ.push(gp)
} else {
stackQ.push(gp)
}
inc++
}
lock(&sched.gFree.lock)
sched.gFree.noStack.pushAll(noStackQ)
sched.gFree.stack.pushAll(stackQ)
sched.gFree.n += inc
unlock(&sched.gFree.lock)
}
}

stackfree

func stackfree(stk stack) {
// ...... if n < fixedStack<<_NumStackOrders && n < _StackCacheSize {
// 小栈(< 32k) 留着复用一下
order := uint8(0)
n2 := n
for n2 > fixedStack {
order++
n2 >>= 1
}
x := gclinkptr(v)
// 如果不使用缓存或当前处理器被抢占,使用全局栈池
if stackNoCache != 0 || gp.m.p == 0 || gp.m.preemptoff != "" {
lock(&stackpool[order].item.mu)
stackpoolfree(x, order)
unlock(&stackpool[order].item.mu)
} else {
// 否则,使用本地缓存
c := gp.m.p.ptr().mcache
if c.stackcache[order].size >= _StackCacheSize {
stackcacherelease(c, order)
}
x.ptr().next = c.stackcache[order].list
c.stackcache[order].list = x
c.stackcache[order].size += n
}
} else {
// 如果栈大小不适合缓存,检查其 span 状态并相应处理
s := spanOfUnchecked(uintptr(v))
if s.state.get() != mSpanManual {
println(hex(s.base()), v)
throw("bad span state")
}
if gcphase == _GCoff {
// 如果 GC 未运行,立即释放栈
osStackFree(s)
mheap_.freeManual(s, spanAllocStack)
} else {
// 如果 GC 运行中,将栈添加到大栈缓存,避免与 GC 竞态
log2npage := stacklog2(s.npages)
lock(&stackLarge.lock)
stackLarge.free[log2npage].insert(s)
unlock(&stackLarge.lock)
}
}
}

Go 内存管理的更多相关文章

  1. .NET基础拾遗(1)类型语法基础和内存管理基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

  2. PHP扩展-生命周期和内存管理

    1. PHP源码结构 PHP的内核子系统有两个,ZE(Zend Engine)和PHP Core.ZE负责将PHP脚本解析成机器码(也成为token符)后,在进程空间执行这些机器码:ZE还负责内存管理 ...

  3. linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址)

    Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同.Linux运行在虚拟存储空间,并负责把系 ...

  4. linux2.6 内存管理——概述

    在紧接着相当长的篇幅中,都是围绕着Linux如何管理内存进行阐述,在内核中分配内存并不是一件非常容易的事情,因为在此过程中必须遵从内核特定的状态约束.linux内存管理建立在基本的分页机制基础上,在l ...

  5. Objective-C内存管理之引用计数

    初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存 ...

  6. Quartz2D内存管理

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px "PingFang SC"; color: #239619 } p.p2 ...

  7. 浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...

  8. linux内存管理

    一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...

  9. cocos2d-x内存管理

    Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一.   为什么需要内存管理 1. new和delete 2. 堆上申 ...

  10. Swift中的可选链与内存管理(干货系列)

    干货之前:补充一下可选链(optional chain) class A { var p: B? } class B { var p: C? } class C { func cm() -> S ...

随机推荐

  1. Flutter 不容错过的 7 大亮点 | Google I/O 精彩回顾

    Flutter 在今年的 Google I/O 上发布了许多重磅更新,欢迎大家和我们一起了解其中不容错过的 7 大亮点, 点击这里 观看 Flutter 不容错过的 7 大亮点 视频了解更多信息. F ...

  2. 以太坊Rollup方案之 arbitrum(2)

    上一期简单介绍了一下rollup的一些基本内容以及aritrun交易的执行流程,这一期将介绍一下aritrum的核心技术 -- 交互式单步证明 这一期主要涉及到的是arbitrum的验证节点 arbi ...

  3. 如何在SQL中查找某一字段在哪些表中

    在SQL中,要找出数据库中包含特定字段(列)的所有表,可以使用数据库的系统表或信息架构视图.不同的数据库系统(如MySQL, SQL Server, PostgreSQL等)有不同的系统表和查询方式. ...

  4. php获取支付宝用户信息

    php获取支付宝用户信息 一:创建应用 要在您的应用中使用支付宝开放产品的接口能力: 您需要先去蚂蚁金服开放平台(open.alipay.com),在开发者中心创建登记您的应用,此时您将获得应用唯一标 ...

  5. Android Perfetto 系列 1:Perfetto 工具简介

    2019 年开始写 Systrace 系列,陆陆续续写了 20 多篇,从基本使用到各个模块在 Systrace 上的呈现,再到启动速度.流畅性等实战,基本上可以满足初级系统开发者和 App 开发者对于 ...

  6. 树形结构体按照 sort 进行排序先按照字母排序 然后按照数字排序

    // 先按照字母排序 然后按照数字排序 function sortListByLetter(arr) { return arr.sort((a, b) => { if (isNaN(a.name ...

  7. vue-template-admin 模板

    1. 替换登录页的样式 2. settings.js 3. layout 文件夹 4. store 文件夹 4.1 app.js

  8. DiTAC:不知如何提升性能?试试这款基于微分同胚变换的激活函数 | ECCV'24

    非线性激活函数对深度神经网络的成功至关重要,选择合适的激活函数可以显著影响其性能.大多数网络使用固定的激活函数(例如,ReLU.GELU等),这种选择可能限制了它们的表达能力.此外,不同的层可能从不同 ...

  9. 别再售卖 5块钱 的 Win10 激活码了,后果很严重

    为了推广Windows 10系统(以下简称Win10),微软过去几年中一直给免费升级,Win7免费洗白的策略现在都还管用. 微软的大方也让很多人忘了Win10系统是要收费的,而且价格不便宜,国内的话, ...

  10. redis的CPA三进二原则

    CAP C:consistency,数据在多个副本中能保持一致的状态. A:Availability,整个系统在任何时刻都能提供可用的服务,通常达到99.99%四个九可以称为高可用 P:Partiti ...