Go语言进阶-内存分配

发布于:2025-02-10 ⋅ 阅读:(55) ⋅ 点赞:(0)

前言

golang中实现的内存分配器,简单来说就是维护了一块大的全局内存,每个线程维护了一块小的私有内存
当需要内存时,会先向私有内存进行申请,若内存不足以分配,则再向全局申请。

基础概念

为了方便自主管理内存,go会先向系统预申请一块内存,然后将内存切割成小块,然后通过内存分配算法管理内存。

以下以64位系统为例,golang程序启动时,会向系统申请的内存如图所示:

前言

golang中实现的内存分配器,简单来说就是维护了一块大的全局内存,每个线程维护了一块小的私有内存
当需要内存时,会先向私有内存进行申请,若内存不足以分配,则再向全局申请。

基础概念

为了方便自主管理内存,go会先向系统预申请一块内存,然后将内存切割成小块,然后通过内存分配算法管理内存。

以下以64位系统为例,golang程序启动时,会向系统申请的内存如图所示:
在这里插入图片描述

预申请的内存划分为 spanbitmaparena 三部分。
其中,arena 即所谓的堆区spanbitmap 用来管理 arena区。

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

span

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

class

根据对象大小,划分了一系列class,每个class都代表一个固定大小的对象,以及每个span的大小。

- class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
- bytes/obj:该class代表对象的字节数
- bytes/span:每个span占用堆的字节数,也即页数*页大小
- objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)
- waste bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)

// class  bytes/obj  bytes/span  objects  waste bytes
//     1          8        8192     1024            0
//     2         16        8192      512            0
//     3         32        8192      256            0
//     4         48        8192      170           32
//     5         64        8192      128            0
//     6         80        8192      102           32
//     7         96        8192       85           32
//     8        112        8192       73           16
//     9        128        8192       64            0
//    10        144        8192       56          128
//    11        160        8192       51           32
//    12        176        8192       46           96
//    13        192        8192       42          128
//    14        208        8192       39           80
//    15        224        8192       36          128
//    16        240        8192       34           32
//    17        256        8192       32            0
//    18        288        8192       28          128
//    19        320        8192       25          192
//    20        352        8192       23           96
//    21        384        8192       21          128
//    22        416        8192       19          288
//    23        448        8192       18          128
//    24        480        8192       17           32
//    25        512        8192       16            0
//    26        576        8192       14          128
//    27        640        8192       12          512
//    28        704        8192       11          448
//    29        768        8192       10          512
//    30        896        8192        9          128
//    31       1024        8192        8            0
//    32       1152        8192        7          128
//    33       1280        8192        6          512
//    34       1408       16384       11          896
//    35       1536        8192        5          512
//    36       1792       16384        9          256
//    37       2048        8192        4            0
//    38       2304       16384        7          256
//    39       2688        8192        3          128
//    40       3072       24576        8            0
//    41       3200       16384        5          384
//    42       3456       24576        7          384
//    43       4096        8192        2            0
//    44       4864       24576        5          256
//    45       5376       16384        3          256
//    46       6144       24576        4            0
//    47       6528       32768        5          128
//    48       6784       40960        6          256
//    49       6912       49152        7          768
//    50       8192        8192        1            0
//    51       9472       57344        6          512
//    52       9728       49152        5          512
//    53      10240       40960        4            0
//    54      10880       32768        3          128
//    55      12288       24576        2            0
//    56      13568       40960        3          256
//    57      14336       57344        4            0
//    58      16384       16384        1            0
//    59      18432       73728        4            0
//    60      19072       57344        3          128
//    61      20480       40960        2            0
//    62      21760       65536        3          256
//    63      24576       24576        1            0
//    64      27264       81920        3          128
//    65      28672       57344        2            0
//    66      32768       32768        1            0
其中,最大的对象是32K大小。超过32K大小的由特殊的class表示,该classID为0,且每个class只包含一个对象。

span的数据结构

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

src/runtime/mheap.go:mspan定义了其数据结构:

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表中的对象大小,也即块大小
}

以class 10 为例,span 和管理的内存如下图所示:
在这里插入图片描述

  • span 的class为10,参照class表,可得出 npages=1, nelems=56, elemsize=144。
  • startAddr 是在span初始化时就指定了某个页的地址
  • allocBits指向一个位图,每位表示一个块是否被分配。
  • nextprev用于将多个span链接起来

cache

span 为管理内存的基本单元,还要有一个数据结构来管理 span,就是 mcentral
各线程需要内存时,需要从mcentral管理的span中申请内存。为了避免多线程申请内存时,不断加锁消耗性能,golang为每个线程分配了span的缓存,这个缓存就是cache

src/runtime/mcache.go:mcache定义了cache的数据结构:

type mcache struct {
    alloc [67*2]*mspan // 按class分组的mspan列表
}
  • allocmspan 的指针数据。数组大小为 class 总数的2倍。
  • 数组中每个元素代表了一种class类型的span列表,每种class类型都有两组span列表。第一组列表中所表示的对象中包含了指针,能够提高GC的扫描性能;另一组则是不包含指针的span列表。

mcache和span的对应关系如下图所示:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

mcache在初始是没有任何span的,在使用过程中,会动态地从central中获取并缓存下来,根据使用情况,每种classspan个数也不相同。
如上图所示,class0span数比class1的要多 ,说明本线程的小对象多一些。

central

cache作为线程的私有资源为单个线程服务,而central则是全局资源,为多个线程服务。
当某个线程内存不足时,会向central申请内存,当某个线程释放内存时又会回收进central

src/runtime/mcentral.go:mcentral定义了central数据结构:

type mcentral struct {
    lock      mutex     //互斥锁
    spanclass spanClass // 每个mcentral管理着一组有相同class的span列表
    nonempty  mSpanList // 指还有内存可用的span列表
    empty     mSpanList // 指没有内存可用的span列表

    nmalloc uint64      // 已累计分配的对象个数
}

线程从central获取span的步骤

  1. 加锁
  2. nonempty列表获取一个可用的span,并将其从链表中删除
  3. 将取出的span放入empty链表
  4. span返回给线程
  5. 解锁
  6. 线程将该span缓存进cache

线程将span归还步骤

  1. 加锁
  2. spanempty列表删除
  3. span加入nonempty列表
  4. 解锁

heap

每个mcentral对象只管理特定的class规格的span
每种class都会对应一种mcentral,这个mcentral的集合存放在mheap中。

src/runtime/mheap.go:mheap定义了heap的数据结构:

type mheap struct {
    lock      mutex

    spans []*mspan            //指向spans区域,用于映射span和page的关系

    bitmap        uintptr     //指向bitmap首地址,bitmap是从高地址向低地址增长的

    arena_start uintptr        //指示arena区首地址
    arena_used  uintptr        //指示arena区已使用地址位置

    central [67*2]struct {
        mcentral mcentral
        pad      [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
    }      //  每种class对应的两个mcentral
}

mheap内存管理如图所示
在这里插入图片描述

系统预分配的内存分为spans、bitmap、arean三个区域,通过mheap管理起来。

内存分配过程

针对待分配对象的大小不同有不同的分配逻辑:

  • (0, 16B) 且不包含指针的对象: Tiny分配
  • (0, 16B) 包含指针的对象:正常分配
  • [16B, 32KB] : 正常分配
  • (32KB, -) : 大对象分配 其中Tiny分配和大对象分配都属于内存管理的优化范畴,这里暂时仅关注一般的分配方法。

以申请size为n的内存为例,分配步骤如下:

  1. 获取当前线程的私有缓存mcache
  2. 根据size计算出适合的class的ID
  3. 从mcache的alloc[class]链表中查询可用的span
  4. 如果mcache没有可用的span则从mcentral申请一个新的span加入mcache中
  5. 如果mcentral中也没有可用的span则从mheap中申请一个新的span加入mcentral
  6. 从该span中获取到空闲对象地址并返回
    预申请的内存划分为 spanbitmaparena 三部分。
    其中,arena 即所谓的堆区spanbitmap 用来管理 arena区。
  • arena 的大小为 512G, 为了方便管理把 arena 区域划分成一个个的 page, 每个 page8KB, 总共 512G/8KB 页。
  • spans 区域存放 span 指针,每个指针对应一个或多个页。所以,span 区域的大小为 *(512GB/8KB)指针大小8byte = 512M
  • bitmap 区域大小也是通过 arena 计算出来,主要用于 GC

流程框图

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到