内存分配和垃圾回收有关,这里我们可以先看一下内存分配。
垃圾回收比较复杂,后面讲。
一开始雨痕大大说了几个基本策略:
- 每次从操作系统申请一大块内存(比如1MB),以减少系统调用。
- 将申请到的大块内存按预定大小预先切分成小块,构成链表。
- 为对象分配内存时,只需从大小合适的链表中提取一个小块即可。
- 回收对象内存时,将该小块内存重新归还到原链表,以便复用。
- 如果闲置内存过多,则尝试归还部分内存给操作系统,降低内存开销。
里面提到了两个概念
span: 就是1提到的大块内存被切成小块的链表。
object: 就是上面说的小块。
这里span虽然是按照小块的规格进行了分级,但实际上采取了比较灵活的策略,可能会在必要条件下把较大规格span的链表的一部分借给较小规格的span,实际合并时也会尝试合并相邻的span。
����golang直接采用tcmalloc的成熟架构。
cache central heap
这里做了三级缓存。
cache是每个golang里面的P搞一个,提供一个无锁分配。
然后central根据sizeclass的大小,把所有中等的object分成若干等级,然后在这里做其中一级缓存,减少计算量。。
heap则是最后一道缓存,在这里发起回收和mmap申请。
然后雨痕给了图说明的内存分布以及初始化的过程。
还顺带普及了一下内联优化。某些简单的函数块可能会被优化掉,里面该从堆分配的,却被弄到了栈分配。。。
cache部分的逻辑:
从代码中可以看出tiny&large object做了特定的处理。tiny是使用classsize=2的span,然后里面使用尽量共享空间,希望一个object能够多复用几次。用cache.tinyoffset,cache.tiny进行控制。
大对象则直接使用堆分配。。
同时还介绍了一个
systemstack(func() {
s = largeAlloc(size,uint32(flags))
})这个使用系统栈来运行的分配函数,保证全局同步。
mcentral部分的逻辑:
这个家伙内部也有两个小缓存。
这还略屌的。
仔细看代码可以知道,mcentral的细节策略还是很多的,比如先考虑nonempty再考虑empty,使用empty时要先来个msweep尝试一下。。。是在不行了才去找heap。。。
mheap部分的逻辑:
和预料的基本一致,作为最后一级的缓存。会先考虑自己的free,不行了,才考虑向系统申请。里面的freelarge是个链表在free没有的时候,会用freelarge,顺序遍历链表找出最合适(在大于目标size里面最小)的freelarge。
#
#
回收
上来就一个综述:回收不会盯着object而是整个span。
大概就是遍历span将不可达object合并到freelist,如果已经回收所有object,则将这块内存还给heap。 细节部分,这里它会考虑同右侧的span进行合并。
释放
这里的入口有sysmon发起。每5分钟都来一次。
大概就是把heap里面的free和freelarge的span list给拿出来干掉。
细节方面这里用了madvise,用来在某些场景下提升性能。
其他
大概就是还有四块系统的部分也需要垃圾回收。 它们自己内部也弄了个二级缓存。 这里就比较简单了,是fix size的,没有span、object、classsize,第一级别用了而是一个chunk,还有复用的list。。。第二级就是使用chunk。 还有个什么record的函数。 大概就是为了提高span的便利效率而做了一个数组类型的h_spans,然后在特定条件下引发扩容,每次扩容 A*3/2 +1。