一文搞懂go内存分配机制

323 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情

前言

在Go语言里,从内存的分配到不再使用后内存的回收等等这些内存管理工作都是由Go在底层完成的。虽然我们在写代码时不必过度关心内存从分配到回收这个过程,但是通过了解他们有助于我们自身的提高,也让我们能写出更高效的Go程序。这篇文章主要介绍Go内存分配和Go内存管理,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

Go内存分配

可以简单的认为Golang程序在启动时,会向操作系统申请一个块内存,分为栈(Stack)和堆(Heap)。栈内存会随着函数的调用分配和回收,堆内存由应用申请分配,由垃圾回收器负责回收。

其中,预申请的内存划分为spans、bitmap、arena三部分,其中arena即为所谓的堆区,应用中需要的内存从这里分配,spans和bitmap是为了管理arena区而存在的,其内存结构如下图所示:

  • arena的大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;
  • spans区域存放span的指针,每个指针对应一个或多个page,所以span区域的大小为(512GB/8KB)*指针大小8byte = 512M
  • bitmap区域大小也是通过arena计算出来,不过主要用于GC。

span

span是用于管理arena页的关键数据结构,每个span中包含1个或多个连续页,为了满足小对象分配,span中的一页会划分更小的粒度,而对于大对象比如超过页大小,则通过多页实现。

span是内存管理的基本单位,每个span用于管理特定的class对象,根据对象大小,span将一个或多个页拆分成多个块进行管理。其数据结构如下:

type mspan struct {
    next *mspan            //链表前向指针,用于将span链接起来
    prev *mspan            //链表前向指针,用于将span链接起来
    startAddr uintptr // 起始地址,也即所管理页的地址
    npages    uintptr // 管理的页数

    nelems uintptr // 块个数,也即有多少个块可供分配

    allocBits  *gcBits //分配位图,每一位代表一个块是否已分配

    allocCount  uint16     // 已分配块的个数
    spanclass   spanClass  // class表中的class ID

    elemsize    uintptr    // class表中的对象大小,也即块大小
}

ext和prev用于将多个span链接起来,这有利于管理多个span。

总结

Golang程序在启动时,会向操作系统申请一个块内存,并划分成spans、bitmap、arena区域:

  • arena区域按页划分成一个个小块
  • span管理一个或多个页
  • mcentral管理多个span供线程申请使用
  • mcache作为线程私有资源,资源来源于mcentral