Skip to content

Latest commit

 

History

History
46 lines (26 loc) · 4.46 KB

内存管理.md

File metadata and controls

46 lines (26 loc) · 4.46 KB

Go是一门带垃圾回收的语言,Go语言中有指针,却没有C中那么灵活的指针操作。大多数情况下是不需要用户自己去管理内存的,但是理解Go语言是如何做内存管理对于写出优秀的程序是大有帮助的。

内存池

  • 首先,它会向操作系统申请大块内存,自己管理这部分内存。
  • 然后,它是一个池子,当上层释放内存时它不实际归还给操作系统,而是放回池子重复利用。
  • 接着,内存管理中必然会考虑的就是内存碎片问题,如果尽量避免内存碎片,提高内存利用率,像操作系统中的首次适应,最佳适应,最差适应,伙伴算法都是一些相关的背景知识。
  • 另外,Go是一个支持goroutine这种多线程的语言,所以它的内存管理系统必须也要考虑在多线程下的稳定性和效率问题。

在多线程方面,很自然的做法就是每条线程都有自己的本地的内存,然后有一个全局的分配链,当某个线程中内存不足后就向全局分配链中申请内存。这样就避免了多线程同时访问共享变量时的加锁。 在避免内存碎片方面,大块内存直接按页为单位分配,小块内存会切成各种不同的固定大小的块,申请做任意字节内存时会向上取整到最接近的块,将整块分配给申请者以避免随意切割。

Go中为每个系统线程分配一个本地的MCache(前面介绍的结构体M中的MCache域),少量的地址分配就直接从MCache中分配,并且定期做垃圾回收,将线程的MCache中的空闲内存返回给全局控制堆。小于32K为小对象,大对象直接从全局控制堆上以页(4k)为单位进行分配,也就是说大对象总是以页对齐的。一个页可以存入一些相同大小的小对象,小对象从本地内存链表中分配,大对象从中心内存堆中分配。

大约有100种内存块类别,每一类别都有自己对象的空闲链表。小于32kB的内存分配被向上取整到对应的尺寸类别,从相应的空闲链表中分配。一页内存只可以被分裂成同一种尺寸类别的对象,然后由空闲链表分配器管理。

分配器的数据结构包括:

  • FixAlloc: 固定大小(128kB)的对象的空闲链分配器,被分配器用于管理存储
  • MHeap: 分配堆,按页的粒度进行管理(4kB)
  • MSpan: 一些由MHeap管理的页
  • MCentral: 对于给定尺寸类别的共享的free list
  • MCache: 用于小对象的每M一个的cache

我们可以将Go语言的内存管理看成一个两级的内存管理结构,MHeap和MCache。上面一级管理的基本单位是页,用于分配大对象,每次分配都是若干连续的页,也就是若干个4KB的大小。使用的数据结构是MHeap和MSpan,用BestFit算法做分配,用位示图做回收。下面一级管理的基本单位是不同类型的固定大小的对象,更像一个对象池而不是内存池,用引用计数做回收。下面这一级使用的数据结构是MCache。

MHeap

MHeap层次用于直接分配较大(>32kB)的内存空间,以及给MCentral和MCache等下层提供空间。它管理的基本单位是MSpan。MSpan是一个表示若干连续内存页的数据结构.

MHeap负责将MSpan组织和管理起来

MCache

MCache层次跟MHeap层次非常像,也是一个分配池,对每个尺寸的类别都有一个空闲对象的单链表。Go的内存管理可以看成一个两级的层次,上面一级是MHeap层次,而MCache则是下面一级。

每个M都有一个自己的局部内存缓存MCache,这样分配小对象的时候直接从MCache中分配,就不用加锁了,这是Go能够在多线程环境中高效地进行内存分配的重要原因。MCache是用于小对象的分配。

MCache层次仅用于分配小对象,分配和释放大的对象则是直接使用MHeap的,跳过MCache和MCentral自由链。MCache和MCentral中自由链的小对象可能是也可能不是清0了的。对象的第2个字节作为标记,当它是0时,此对象是清0了的。页堆中的总是清零的,当一定范围的对象归还到页堆时,需要先清零。这样才符合Go语言规范:分配一个对象不进行初始化,它的默认值是该类型的零值。

MCentral

MCentral层次是作为MCache和MHeap的连接。对上,它从MHeap中申请MSpan;对下,它将MSpan划分成各种小尺寸对象,提供给MCache使用。