Go语言内存管理 | 青训营笔记

106 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第5天

堆内存

  1. Golang堆内存分配使用了类似tcmalloc内存分配器的算法

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

3、golang默认将内存分为68种大小规格,最小8B,最大32KB,大于32的分配给一种类型(0),同样的大小分为可扫描和不可扫描( 标量和指针),总共有 136 个 mspans。

  1. 一个arena被划分为多个span,一个span包含1个或多个page,固定划分为一定规格的内存块。

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

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

中央 mcentral

image.png

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

image.png

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

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

image.png

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

HeapArena结构

一个arena对应一个heapArena结构

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
}

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

跨度 size为8192,每个index对应一个page,用于确定某个Page对应的mspan

页面使用中 长度为 1024 字节(8192 位),标记正在使用的跨度的第一页。 (通过它可以知道有多少个span,以及每个span的页数)

页面标记 标记每个跨度的第一页。 在GC标记阶段,这个位图会被修改以标记哪些span包含被标记的对象; 在GC清理阶段,没有被marked objects的span会根据这个bitmap进行释放。

跨度 一个span对应一个mspan结构体,用于管理span中一组连续的页面。

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 (less than 16kB && noscan) 使用P本身的tiny分配,不够从mcache中获取,再从mcentral中获取

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

large (>32kB) 直接申请相应页码page的mheap(堆内存)

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

  1. linux on amd64最多有4096个arena,你找多少arena?

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

    即 arenaIndex = (p-arenaBaseOffset) / heapArenaBytes

2、如何判断当前p属于哪个页面?

 找到arena后,需要判断当前p属于arena中的哪个page。

 pagesPerArena是每个arena的页面数,pageSize是每个页面的大小

pageIndex = (p / pageSize) % pagesPerArena

4.收尾工作 如果处于GC标记阶段,需要标记新分配的对象(GC屏障机制),如果满足GC触发条件,则需要进行GC标记。