go内存管理详解

2022-08-19 14:58:47 浏览数 (2)

堆内存

1.golang堆内存分配采用和tcmalloc内存分配器类似的算法

2.堆内存划分为一个个arena空间,arena的初始地址记录在arenaBaseOffset中,在amd64架构的linux中,其值默认为64M,每个arena中有8192个page,每个page有8KB。

3.golang将内存默认分为68种大小规格,最小为8B,最大为32KB,大于32的独立分给一种类型(0),同一种规格又区分为可扫描和不可扫描(标量和指针),所有总共有136种mspan。

4.一个arena划分为多个span,一个span包含1到多个page,并固定划分为某种规格的内存块。

mheap

mheap用于管理整个堆内存,一个arena对应一个heapArena结构,一个span对应一个mspan结构。通过它们可以知道某个内存块是否已分配;已分配的内存用作指针还是标量;是否已被GC标记;是否等待清扫等信息.

central

mheap中有一个全局的mspan管理中心---mheap.central,是一个长度为136(68*2)的数组,数组结构是一个mcentral结构 padding,其作用是为了方便取用各种规格的mspan。

mcentral

一个mcentral对应一种mspan类型,记录在spanclass中,spanclass的结构:

full和partial分别表示已用尽和未用尽,而每个结构里面包含两个并发安全的spanSet,分别表示已清扫和未清扫。

为了降低多个P之间的竞争,所有对象都会有自己的本地小对象缓存mcache。

mcache中存在tiny和alloc结构,tiny用于分配小于16B的对象,alloc是一个长度为136的数组,数组元素是mspan结构。当本地mspan没有空余的时候,回去向全局的mcentral中申请(partial)新的mspan。将已经用尽的mspan替换到full中。

class对应表格

代码语言:javascript复制
// class  bytes/obj  bytes/span  objects  waste bytes
//     1          8        8192     1024            0
//     2         16        8192      512            0
//     3         32        8192      256            0
//     4         48        8192      170           32
//     5         64        8192      128            0
//     6         80        8192      102           32
//     7         96        8192       85           32
//     8        112        8192       73           16
//     9        128        8192       64            0
//    10        144        8192       56          128
//    11        160        8192       51           32
//    12        176        8192       46           96
//    13        192        8192       42          128
//    14        208        8192       39           80
//    15        224        8192       36          128
//    16        240        8192       34           32
//    17        256        8192       32            0
//    18        288        8192       28          128
//    19        320        8192       25          192
//    20        352        8192       23           96
//    21        384        8192       21          128
//    22        416        8192       19          288
//    23        448        8192       18          128
//    24        480        8192       17           32
//    25        512        8192       16            0
//    26        576        8192       14          128
//    27        640        8192       12          512
//    28        704        8192       11          448
//    29        768        8192       10          512
//    30        896        8192        9          128
//    31       1024        8192        8            0
//    32       1152        8192        7          128
//    33       1280        8192        6          512
//    34       1408       16384       11          896
//    35       1536        8192        5          512
//    36       1792       16384        9          256
//    37       2048        8192        4            0
//    38       2304       16384        7          256
//    39       2688        8192        3          128
//    40       3072       24576        8            0
//    41       3200       16384        5          384
//    42       3456       24576        7          384
//    43       4096        8192        2            0
//    44       4864       24576        5          256
//    45       5376       16384        3          256
//    46       6144       24576        4            0
//    47       6528       32768        5          128
//    48       6784       40960        6          256
//    49       6912       49152        7          768
//    50       8192        8192        1            0
//    51       9472       57344        6          512
//    52       9728       49152        5          512
//    53      10240       40960        4            0
//    54      10880       32768        3          128
//    55      12288       24576        2            0
//    56      13568       40960        3          256
//    57      14336       57344        4            0
//    58      16384       16384        1            0
//    59      18432       73728        4            0
//    60      19072       57344        3          128
//    61      20480       40960        2            0
//    62      21760       65536        3          256
//    63      24576       24576        1            0
//    64      27264       81920        3          128
//    65      28672       57344        2            0
//    66      32768       32768        1            0
  • class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
  • bytes/obj:该class代表对象的字节数
  • bytes/span:每个span占用堆的字节数,也即页数*页大小
  • objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)
  • waste bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)

超过32K大小的对象class为0。

HeapArena结构

一个arena对应一个heapArena结构

代码语言:javascript复制
type heapArena struct {
  bitmap       [heapArenaBitmapBytes]byte
  spans        [pagesPerArena]*mspan
  pageInUse    [pagesPerArena / 8]uint8
  pageMarks    [pagesPerArena / 8]uint8
  pageSpecials [pagesPerArena / 8]uint8
  checkmarks   *checkmarksMap
  zeroedBase   uintptr
}

bitmap

用一字节标记arena中4个指针大小的内存空间:低4位用于标记指针/标量;高4位用于标记扫描/终止(后续单元是否包含指针)

spans

大小为8192,每一个index对应一个page,用于确定某一个Page对应的mspan是什么

pageInUse

长度为1024字节(8192位),标记处于使用状态的span的第一个page。(通过它可以知道有几个span,每个span的页数)

pageMarks

标记每个span的第一个page,在GC标记阶段会修改这个位图,标记哪些span中存在被标记的对象;在GC清扫阶段会根据这个位图,来释放不含标记对象的span。

mspan

一个span对应一个mspan结构,用于管理span中的一组连续的page。

代码语言:javascript复制
type mspan struct {
	next           *mspan
	prev           *mspan
	....
	freeindex      uintptr // 下一个空闲的内存块地址
	nelems         uintptr // 当前mspan的内存块个数
	....
    // allocBits is a bitmap of objects in this span.
    // If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
    // then object n is free;
	allocBits      *gcBits(unit8)	// 标记哪些内存块被使用(分配)了
	gcmarkBits     *gcBits(unit8)	// gc标记位图
	....
	spanclass      spanClass // 同mcentral,当前span的数据格式
	state          mSpanStateBox // 表示此mspan的类型 1表示堆内存,2表示栈内存
    elemsize       uintptr // class表中的对象大小,也即块大小
	....
}

allocBits:是一个uint8类型,用位图标记哪些内存已经被分配使用了(他是数组的首地址)。

mallocgc函数

1.辅助GC

当GC标记速率小于堆内存申请速率时,会要求当前Go携程执行辅助GC工作,每次执行至少标记64KB的内存。辅助标记的内存大小会成为信用额度,后面在申请小于该内存时,不会再执行辅助GC。 对于特殊Go携程,可以窃取全局的信用额度,而逃避辅助GC。

2.空间分配

tiny(小于16kB && noscan) 使用P自身的tiny分配,不够向mcache拿,在没有向mcentral中再拿

normal([16kB,32KB]) mspan分配

large (> 32kB) 直接向mheap(堆内存)申请对应页数page

3.位图标记 如何通过堆内存地址(p),找到对应的mspan进行标记回收?

1.amd64上面linux最多有4096个arena,寻找p在第几个arena?

arenaBaseOffset是arena的起始地址,heapArenaBytes是每个arena的大小。

即 arenaIndex = (p-arenaBaseOffset) / heapArenaBytes

2.如何确定当前p属于哪个page?

找到arena之后,需要确定当前p属于arena中哪一页。

pagesPerArena是每个arena中拥有的page数量,pageSize每一页的大小

pageIndex = (p / pageSize) % pagesPerArena

4.收尾工作

如果处于GC标记阶段,就需要对新分配的对象进行标记(GC屏障机制),如果达到GC触发条件,还需要执行一次GC标记。

栈内存

栈内存同堆内存相似,栈内存也使用mspan来管理内存,只是mspan.state来区分此mspan是栈还是堆。

栈内存在初始化时会使用两个全局的栈分配对象:stackpool 和 stackLarge。

小于32KB的内存由stackpool进行分配

>=32KB的内存由stackLarge进行分配

stackpool

代码语言:javascript复制
var stackpool [_NumStackOrders]struct {
	item stackpoolItem
	_    [cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize]byte
}

type stackpoolItem struct {
 mu mutex
 span mSpanList
}

stackpool在linux被分成 2KB 4KB 8KB 16KB 的数组(2的幂次方)

stackLarge

代码语言:javascript复制
var stackLarge struct {
	lock mutex
	free [heapAddrBits - pageShift]mSpanList // free lists by log_2(s.npages)
}

stackLarge中free 是一个[25]mSpanList的数组,他的第0和1位是不会使用的,从8KB开始,下一位是上一位的两倍,是为了方便使用 log2(index)=pageNum。

分配顺序和堆内存相似

<32KB时 本地缓存(P)的 stackcache -> 全局的stackpool ->全局堆内存中申请

>=32KB时 计算所需要的Pgae数目,然后利用上诉公式,在stackLarge中找到对应的index,获取一个mspan

栈增长

栈增长是成倍增长,基础为2KB,每次增长为前一次的两倍,同时将状态置为_Gcopystack,然后调用 copystack 将原本数据复制到新的栈空间,再释放旧的栈空间。

栈收缩

栈收缩发生在GC阶段,最小只会缩到2KB(栈初始大小),可以安全收缩时,则会马上执行栈收缩,否则会设置栈收缩标记:preemptShrink = true,在携程让出cpu时,会检测此参数,进行栈收缩操作。

栈释放

栈释放发生在协程栈运行结束的时候。

>=32KB的栈,如果在GC清扫结算,直接返回给堆内存,否则归还给stackLarge

<32KB的栈,优先归还本地stackcache,如果本地满了归还全局stackpool,再满了就归还到全局堆内存中

引用:

https://www.bilibili.com/video/BV1av411G7pB?spm_id_from=333.999.0.0

https://www.bookstack.cn/read/GoExpertProgramming/chapter04-4.1-memory_alloc.md

0 人点赞