golang 语言 gc基础,内存分配原理

内存分配原理

一、基本情况

  1. 内存分配器有glibc提供的ptmalloc2,谷歌提供的tcmalloc,脸书提供的jemalloc
  2. golang中提供了内存分配器,原理与tcmalloc类似,简单说维护一块大的全局内存,每个线程维护一块小的私有内存,私有内存不足再从全局申请
  3. 内存分配与GC(垃圾回收)有密切关系,所以,了解GC前需要了解内存分配的原理

二、基本概念

  1. 为了golang自主管理内存,先向系统申请一块内存,然后将内存切割成小块,通过一定的内存分配算法管理内存。golang 语言 gc基础,内存分配原理_第1张图片
  2. 申请的内存划分为三个部分,spans,bitmap,arena.其中arena为堆区,程序中需要的内存从这里分配。
  3. spans,bitmap为了管理arena而存在。arena大小为512G,为了管理arena区域,区域划分成一个个page,每个page 8k,共512G/8k
  4. spans 区域存放span指针,每个指针对应一个或多个page,所以span区域的大小为 512G/8k指针大小8byte=512M
  5. bitmap区域大小通过arena计算出来,一般用于GC垃圾回收

三、 span

  1. span用于管理arena页的数据结构,每个span包含一个或多个连续分页。为了满足小对象的分配,span中的一页会划分更小的粒度。

  2. 根据对象分配大小,划分一系列的class ,每个class表示一个固定大小的对象,最大的对象是32k,超过32K的用特殊的class表示,class ID =0,每个span包含一个对象。

    // 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
    
    1. class:class ID,每个span结构都有个class ID,表示span可以处理的对象类别
    2. bytes/obj:该 class对应的对象字节数
    3. bytes/span:每个span占用堆的字节数
    4. object:每个span可以分配的对象个数
    5. waste bytes:每个span产生的内部碎片
  3. span数据结构

    1. src/runtime/mheap.go

      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表中的对象大小,也即块大小
      }
      
    2. 以 class 10为例,span和管理的内存如下golang 语言 gc基础,内存分配原理_第2张图片

    3. class 10 ,参照class表得出npages=1,nelems=56,elemsize=144

  4. cache

    1. 有了管理内存的基本单位span,还要有个数据结构管理span, mcentral,各个线程从mcentral管理的span中申请内存,为了避免锁的竞争,go为每个线程分配span缓存,即cache.

    2. src/runtime/mcache.go:mcache

      type mcache struct {
          alloc [67*2]*mspan // 按class分组的mspan列表
      }
      
    3. alloc为mspan的指针数组,数组大小为class总数的2倍。每种class类型都有两组span列表,第一组列表中所表示的对象中包含了指针,第二组列表中所表示的对象不含有指针,这么做是为了提高GC扫描性能,对于不包含指针的span列表,没必要去扫描。

    4. 根据对象是否包含指针,将对象分为noscan和scan两类,其中noscan代表没有指针,而scan则代表有指针,需要GC进行扫描。golang 语言 gc基础,内存分配原理_第3张图片

  5. central

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

    2. src/runtime/mcentral.go:mcentral

      type mcentral struct {
          lock      mutex     //互斥锁
          spanclass spanClass // span class ID
          nonempty  mSpanList // non-empty 指还有空闲块的span列表
          empty     mSpanList // 指没有空闲块的span列表
      
          nmalloc uint64      // 已累计分配的对象个数
      }
      
    3. lock: 线程间互斥锁,防止多线程读写冲突

    4. spanclass : 每个mcentral管理着一组有相同class的span列表

    5. nonempty: 指还有内存可用的span列表

    6. empty: 指没有内存可用的span列表

    7. nmalloc: 指累计分配的对象个数

      线程从central获取span步骤如下:

    8. 加锁

    9. 从nonempty列表获取一个可用span,并将其从链表中删除

    10. 将取出的span放入empty链表

    11. 将span返回给线程

    12. 解锁

    13. 线程将该span缓存进cache

    线程将span归还步骤

    1. 加锁
    2. 将span从empty列表删除
    3. 将span加入noneempty列表
    4. 解锁
  6. 从mcentral数据结构可见,每个mcentral对象只管理特定的class规格的span。事实上每种class都会对应一个mcentral,这个mcentral的集合存放于mheap数据结构中

    1. src/runtime/mheap.go:mheap

      type mheap struct {
          lock      mutex
      
          spans []*mspan
      
          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
          }
      }
      
    2. lock: 互斥锁

    3. spans: 指向spans区域,用于映射span和page的关系

    4. bitmap:bitmap的起始地址

    5. arena_start: arena区域首地址

    6. arena_used: 当前arena已使用区域的最大地址

    7. central: 每种class对应的两个mcentral

    8. 从数据结构可见,mheap管理着全部的内存,事实上Golang就是通过一个mheap类型的全局变量进行内存管理的。

    9. golang 语言 gc基础,内存分配原理_第4张图片

  7. 内存分配过程

    1. 对待分配对象的大小不同有不同的分配逻辑

    2. (0, 16B) 且不包含指针的对象: Tiny分配

    3. (0, 16B) 包含指针的对象:正常分配

    4. [16B, 32KB] : 正常分配

    5. (32KB, -) : 大对象分配
      其中Tiny分配和大对象分配都属于内存管理的优化范畴,这里暂时仅关注一般的分配方法。

      以申请size为n的内存为例

    6. 获取当前线程的私有缓存mcache

    7. 根据size计算出适合的class的ID

    8. 从mcache的alloc[class]链表中查询可用的span

    9. 如果mcache没有可用的span则从mcentral申请一个新的span加入mcache中

    10. 如果mcentral中也没有可用的span则从mheap中申请一个新的span加入mcentral

    11. 从该span中获取到空闲对象地址并返回

  8. 总结

    1. Golang内存分配是个相当复杂的过程,其中还掺杂了GC的处理。
    2. Golang程序启动时申请一大块内存,并划分成spans、bitmap、arena区域
    3. arena区域按页划分成一个个小块
    4. span管理一个或多个页
    5. mcentral管理多个span供线程申请使用
    6. mcache作为线程私有资源,资源来源于mcentral

你可能感兴趣的:(golang,数据结构,操作系统,golang,java,数据结构)