go内存分配器

程序中的数据和变量都会被分配到程序所在的虚拟内存中,内存空间包含两个重要区域:栈区(Stack)和堆区(Heap)。函数调用的参数、返回值以及局部变量大都会被分配到栈上(可能发生内存逃逸),这部分内存会由编译器进行管理;不同编程语言使用不同的方法管理堆区的内存,C++ 等编程语言会由工程师主动申请和释放内存,Go 以及 Java 等编程语言会由工程师和编译器共同管理,堆中的对象由内存分配器分配并由垃圾收集器回收。

内存分配方法

  • 线性分配法

    只在内存中维护一个指针,并指向剩余空闲内存地址,如果分配新的内存空间只需要将指针往后移即可,如果释放内存,需要通过额外操作来整理内存碎片。(线性分配器本身是无法合理利用已经被释放的内存。)

  • 空闲链表分配法

    空闲内存块之间通过指针相连,相比于线性分配法,可以重用已经被释放的内存。

  • go的内存分配策略:隔离适应策略

    对不同大小的内存块分级,减少内存块链表的长度。分配时,先根据内存大小直接定位到特定的内存块链表头节点,然后在链表里查找空闲的内存块。(类似于二分的思想,可以减少遍历内存块的数量,提升效率)

多级缓存

内存分配会根据所需内存的大小,从不同级别缓存申请内存。

  • 线程缓存

    线程缓存属于每一个独立的线程内,并没有并发安全问题,申请很快。

  • 中心缓存

    中心缓存可能涉及到多个线程并发申请,需要加锁访问。

  • 页堆

    前面两种无法满足内存申请,则需要从页堆申请新的内存空间。

分级分配

go划分了对象大小的级别,按照对象的不同大小,采用不同的分配策略。

  • 微对象(0, 16B]

    先使用微型分配器,再依次尝试线程缓存、中心缓存和堆分配内存;

  • 小对象(16B, 32KB]

    依次尝试使用线程缓存、中心缓存和堆分配内存;

  • 大对象(32KB, +♾️)

    大于32KB的内存分配申请,会直接在堆上申请。

go的虚拟内存布局

暂略