精确的垃圾回收
像C语言这种不支持垃圾回收的语言,其实还是有些垃圾回收的库可以使用的。这类库一般也是用的标记清扫算法实现的,但是它们都是保守的垃圾回收。之所以叫“保守”是因为它们没办法获取对象类型信息,因此只能保守地假设地址区间中每个字都是指针。
无法获取对象的类型信息会造成什么问题呢?这里举个例子来说明。
假设某个结构体中是不包含指针成员的,那么对该结构体成员进行垃圾回收时,其实是不必要递归地标记结构体的成员的。但是由于没有类型信息,我们并不知道这个结构体成员不包含指针,因此我们只能对结构体的每个字节递归地标记下去,这显然会浪费很多时间。这个例子说明精确的垃圾回收可以减少不必要的扫描,提高标记过程的速度。
这个例子说明,保守的垃圾回收某些情况下会出现垃圾无法被回收的情况。虽然不会造成大的问题,但总是让人很不爽,都是没有类型信息惹的祸。
现在好了,在Go语言的 1.1 版本中开始支持精确的垃圾回收。精确的垃圾回收首先需要的就是类型信息,上一节中讲过 MSpan 结构体,类型信息是存储在 MSpan 中的。从一个地址计算它所属的 MSpan,公式如下:
页号 = (地址 - mheap.arena_start) >> 页大小
MSpan = mheap->map[页号]
1.接下来通过 MSpan->type 可以得到分配块的类型。这是一个 MType 的结构体:
struct MTypes { byte compression; // one of MTypes_* bool sysalloc; // whether (void*)data is from runtime·SysAlloc uintptr data; };
2.MTypes
MTypes 描述 MSpan 里分配的块的类型,其中 compression 域描述数据的布局。它的取值为 MTypes_Empty、MTypes_Single、MTypes_Words、MTypes_Bytes 四个中的一种:
MTypes_Empty:所有的块都是 free 的,或者这个分配块的类型信息不可用。这种情况下 data 域是无意义的。
MTypes_Single:这个 MSpan 只包含一个块,data 域存放类型信息,sysalloc 域无意义。
MTypes_Words:这个 MSpan 包含多个块(块的种类多于 7)。这时 data 指向一个数组 [NumBlocks]uintptr,数组里每个元索存放相应块的类型信息。
MTypes_Bytes:这个 MSpan 中包含最多 7 种不同类型的块。这时 data 域指下面这个结构体
struct { type [8]uintptr // type[0] is always 0 index [NumBlocks]byte }
第 i 个块的类型是 data.type[data.index[i]]
表面上看 MTypes_Bytes 好像最复杂,其实这里的复杂程度是 MTypes_Empty 小于 MTypes_Single 小于 MTypes_Bytes 小于 MTypes_Words 的。MTypes_Bytes 只不过为了做优化而显得很复杂。
上一节中说过,每一块 MSpan 中存放的块的大小都是一样的,不过它们的类型不一定相同。如果没有使用,那么这个 MSpan 的类型就是 MTypes_Empty。如果存一个很大块,大于这个 MSpan 大小的一半,因此存不了其它东西了,那么这个 MSpan 的类型是 MTypes_Single。
假设存了多种块,每一块用一个指针,本来可以直接用 MTypes_Words 存的。但是当类型不多时,可以把这些类型的指针集中起来放在数组中,然后存储数组索引。这是一个小的优化,可以节省内存空间。
得到的类型信息最终其实是一个这样的结构体:
struct Type { uintptr size; uint32 hash; uint8 _unused; uint8 align; uint8 fieldAlign; uint8 kind; Alg *alg; void *gc; String *string; UncommonType *x; Type *ptrto; };
不同类型的类型信息结构体略有不同,这个是通用的部分。可以看到这个结构体中有一个 gc 域,精确的垃圾回收就是利用类型信息中这个 gc 域实现的。
写在最后
最后:
🐱对于刚刚入门的同学,❀多少听起来会有点迷糊,话🌷但是没关系,不妨去牛客看一看对于go新生肯定会有很大的帮助滴。不要着急,不要慌,月亮下面有月光,循着月光找月亮,🌜🌜🌜快来🐂牛客🐂吧!