Golang 内存分配

172 阅读12分钟

分页存储

分页存储管理是操作系统中重要的内容,其核心思想是将进程的地址空间分为若干个固定大小的页,每页称为一个“页框”,或称“页帧”、“内存块”、“物理块”。每个页框都有一个编号,即“页框号”。同时,将虚拟和物理内存空间划分为同样大小的页和帧,并建立映射方案,实现物理地址和逻辑地址的转换。这种存储方式可以提高内存交换效率减少内存碎片。但也会增加页表的存储空间和数据复制的成本,可能出现抖动现象

虚拟地址

在计算机系统中,虚拟地址空间是由操作系统管理的,当一个用户进程被创建时,操作系统会为其分配一段连续且独立的虚拟地址空间都从0开始,进程之间相互隔离。用户进程可以通过访问连续的虚拟地址来访问物理内存中的不同数据和指令。

内存分页

操作系统使用内存分页技术。内存被分割成大小相等的块,称为页。每个进程的虚拟地址空间也被分成大小相等的块,称为虚拟页。操作系统使用页表来映射虚拟页到物理页(实际内存中的页)的对应关系。每个进程的页表都是独立的,使得不同进程的虚拟页可以映射到不同的物理页,实现了内存隔离。

内存保护

为了防止进程之间相互干扰和访问彼此的数据,操作系统通过内存保护机制来限制每个进程对内存的访问。在页表中,可以设置页的访问权限,比如只读、可写、可执行等。操作系统会根据进程的访问权限来设置页表项,确保进程只能访问自己所拥有的内存区域,而不能访问其他进程的内存。

进程切换

在多道程序环境下,CPU会在不同的进程之间进行切换,以实现并发执行。当一个进程被调度执行时,操作系统会将该进程的页表装载到内存管理单元(MMU)中,从而使得进程的虚拟地址能正确映射到物理地址。当进程切换时,MMU会切换到下一个进程的页表,从而实现进程间内存隔离。

内存分配

这次讨论的内存分配主要是堆区内存分配,Golang 采取内存预分配策略,page作为组成堆区mheap的基本单位,每个page大小8k,若干个page组成一个mspan,mspan一共有68种规格之所以将mspan划分为不同规格主要解决减少内存碎片,提高内存的利用率

// class  bytes/obj  bytes/span  objects  tail waste  max waste  min align
//     1          8        8192     1024           0     87.50%          8
//     2         16        8192      512           0     43.75%         16
//     3         24        8192      341           8     29.24%          8
//     4         32        8192      256           0     21.88%         32
//     5         48        8192      170          32     31.52%         16
//     6         64        8192      128           0     23.44%         64
//     7         80        8192      102          32     19.07%         16
//     8         96        8192       85          32     15.95%         32
//     9        112        8192       73          16     13.56%         16
//    10        128        8192       64           0     11.72%        128
//    11        144        8192       56         128     11.82%         16
//    12        160        8192       51          32      9.73%         32
//    13        176        8192       46          96      9.59%         16
//    14        192        8192       42         128      9.25%         64
//    15        208        8192       39          80      8.12%         16
//    16        224        8192       36         128      8.15%         32
//    17        240        8192       34          32      6.62%         16
//    18        256        8192       32           0      5.86%        256
//    19        288        8192       28         128     12.16%         32
//    20        320        8192       25         192     11.80%         64
//    21        352        8192       23          96      9.88%         32
//    22        384        8192       21         128      9.51%        128
//    23        416        8192       19         288     10.71%         32
//    24        448        8192       18         128      8.37%         64
//    25        480        8192       17          32      6.82%         32
//    26        512        8192       16           0      6.05%        512
//    27        576        8192       14         128     12.33%         64
//    28        640        8192       12         512     15.48%        128
//    29        704        8192       11         448     13.93%         64
//    30        768        8192       10         512     13.94%        256
//    31        896        8192        9         128     15.52%        128
//    32       1024        8192        8           0     12.40%       1024
//    33       1152        8192        7         128     12.41%        128
//    34       1280        8192        6         512     15.55%        256
//    35       1408       16384       11         896     14.00%        128
//    36       1536        8192        5         512     14.00%        512
//    37       1792       16384        9         256     15.57%        256
//    38       2048        8192        4           0     12.45%       2048
//    39       2304       16384        7         256     12.46%        256
//    40       2688        8192        3         128     15.59%        128
//    41       3072       24576        8           0     12.47%       1024
//    42       3200       16384        5         384      6.22%        128
//    43       3456       24576        7         384      8.83%        128
//    44       4096        8192        2           0     15.60%       4096
//    45       4864       24576        5         256     16.65%        256
//    46       5376       16384        3         256     10.92%        256
//    47       6144       24576        4           0     12.48%       2048
//    48       6528       32768        5         128      6.23%        128
//    49       6784       40960        6         256      4.36%        128
//    50       6912       49152        7         768      3.37%        256
//    51       8192        8192        1           0     15.61%       8192
//    52       9472       57344        6         512     14.28%        256
//    53       9728       49152        5         512      3.64%        512
//    54      10240       40960        4           0      4.99%       2048
//    55      10880       32768        3         128      6.24%        128
//    56      12288       24576        2           0     11.45%       4096
//    57      13568       40960        3         256      9.99%        256
//    58      14336       57344        4           0      5.35%       2048
//    59      16384       16384        1           0     12.49%       8192
//    60      18432       73728        4           0     11.11%       2048
//    61      19072       57344        3         128      3.57%        128
//    62      20480       40960        2           0      6.87%       4096
//    63      21760       65536        3         256      6.25%        256
//    64      24576       24576        1           0     11.45%       8192
//    65      27264       81920        3         128     10.00%        128
//    66      28672       57344        2           0      4.91%       4096
//    67      32768       32768        1           0     12.50%       8192



内存布局

下图展示Golang进程的虚拟内存空间布局,整个堆区mheap组成包含arenascentralmcache,它们都有自己的数据结构管理各自内存空间,对内存合理组织无论是在提升内存利用率还是减少内存碎片或者gc垃圾回收等方面

未命名文件.png

mheap数据结构

type mheap struct {
    //全局锁
    lock      mutex
    //页面分配的数据结构
    pages     pageAlloc
    //所有的mspan结构指针
    allspans []*mspan
    .......
    //heapArena结构指针数组
    arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
    .......
    //当前区的开始和结束地址
    curArena struct {
        base, end uintptr
    }
    //mcentral结构数组
    //在Go语言的内存管理中,mspan是最小的内存分配单元,而mcentral则是全局的mspan管理者,负责为所有mcache提供所有线程的内存管理
    //numSpanClasses的值是由Go语言运行时(runtime)预定义的一个常量,其值为68*2=136。这个值实际上代表了mspan的类别数,每个类别对应着不同大小的内存块。具体来说,Go语言将mspan分为了67个不同的类别,每个类别的大小都是2的幂次方,从32KB到1GB不等。其中,第0号和第136号类别是保留类别,不用于实际的内存分配。因此,实际上可用的mspan类别数为68个
    //在这68个可用的mspan类别中,有68个是需要扫描的中心缓存(即包含指针的内存块),另外的68个则是不需要扫描的中心缓存(即不包含指针的内存块)。需要扫描的中心缓存会在垃圾回收时被扫描,以查找未被引用的对象并释放它们占用的内存。而不需要进行扫描的中心缓存则可以直接释放它们占用的内存,因为它们不包含任何指针
    //通过将mspan划分为不同的类别,Go语言可以更加高效地管理和分配内存,避免了内存碎片的问题,并提高了内存使用的效率
    central [numSpanClasses]struct {
        mcentral mcentral
        pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
    }
    .......
}
​
​
​

arenas数据结构

type heapArena struct {
  // 位图,详见下文
  bitmap [heapArenaBitmapBytes]byte
  //是一个8192(pagesPerArena)大小的指针数组,每个mspan对应8KB
  //这是只是表示有这么多mspan,并不是指一个mspan只有一个page
  //在mheap分配内存的时候可能n个page对应一个mspan,会在mheap_.alloc.allocSpan.setSpans中将这个mspan的指针,对应到heapArena.spans的n个位置
  spans [pagesPerArena]*mspan
​
  //表示page是否被使用 = 8192 / 8 = 1024
  pageInUse [pagesPerArena / 8]uint8//页是否被标记,gc使用
  pageMarks [pagesPerArena / 8]uint8//又是一个与pageInUse类似的位图,只不过标记的是哪些span包含特殊设置,目前主要指的是包含finalizers,或者runtime内部用来存储heap profile数据的bucket。
  pageSpecials [pagesPerArena / 8]uint8//一个大小为1MB的位图,其中每个二进制位对应arena中一个指针大小的内存单元。当开启调试debug.gccheckmark的时候,checkmarks位图用来存储GC标记的数据。该调试模式会在STW 的状态下遍历对象图,用来校验并发回收器能够正确地标记所有存活的对象。
  checkmarks *checkmarksMap
​
  // 记录的是当前arena中下个还未被使用的页面的位置,相对于arena起始地址的偏移量。页面分配器会按照地址顺序分配页面,所以zeroedBase之后的页面都还没有被用到,因此还都保持着清零的状态。通过它可以快速判断分配的内存是否还需要进行清零
  zeroedBase uintptr
}
​

mcentral数据结构

//go:notinheap
type mcentral struct {
  spanclass spanClass //当前mcentral是哪一种spanclass
 //在Go语言中,内存管理是通过垃圾回收器(Garbage Collector)来自动处理的。垃圾回收器会定期扫描内存中的 span,并释放不再被引用的对象所占用的内存。
//partial [2]spanSet 和full [2]spanSet是用于跟踪内存使用情况的两个列表。它们分别表示具有空闲对象和没有空闲对象的spanSet。
//partial [2]spanSet:这个列表包含了一些span,这些span中仍然有未被释放的对象。这些对象可能是由于程序逻辑错误或延迟释放导致的。通过跟踪这些span,可以识别出潜在的内存泄漏问题,并进行相应的修复。
//full    [2]spanSet:这个列表包含了一些span,这些span中的所有对象都已经被释放,没有任何剩余的对象需要管理。这意味着这些跨度已经完全清空,可以被安全地回收。
//通过设置这两个列表,可以更好地了解内存的使用情况,并及时发现和解决潜在的内存问题。 
  partial [2]spanSet // 有可用空间的span集合
  full    [2]spanSet // 没可用空间的span集合 或者当前链表里的Span已经交给mcache
}
​

mcache数据结构

//go:notinheap
type mcache struct {
​
  nextSample uintptr // 堆分析的下一个采样
  scanAlloc  uintptr // 用来指示已分配堆的扫描情况// 微对象分配相关,详见微对象分配
  tiny       uintptr
  tinyoffset uintptr
  tinyAllocs uintptr
​
  alloc [numSpanClasses]*mspan // 136个span链表
​
  stackcache [_NumStackOrders]stackfreelist //栈相关
​
  flushGen uint32
}
​

如何分配mcache

//1.首先,定义了一个指向mcache类型的指针变量c。
//2.然后,调用systemstack函数,这是一个用于在系统堆栈上执行操作的函数。在这个函数中,首先获取了mheap_的锁,然后从mheap_.cachealloc中分
//3.配了一块内存,并将其转换为mcache类型,赋值给c。同时,将mheap_.sweepgen赋值给c.flushGen。最后,释放了mheap_的锁。
//4.接下来,遍历c.alloc数组,将其每个元素设置为&emptymspan。这可能是为了初始化或重置这个数组。
//5.然后,调用nextSample()函数,并将返回值赋给c.nextSample。这可能是为了设置或更新内存缓存的下一个样本。
//6.最后,返回c。
//这段代码的主要目的是创建一个新的内存缓存,并对其进行初始化和配置。
func allocmcache() *mcache {
  var c *mcache
  systemstack(func() {
    lock(&mheap_.lock)
    c = (*mcache)(mheap_.cachealloc.alloc())
    c.flushGen = mheap_.sweepgen
    unlock(&mheap_.lock)
  })
  for i := range c.alloc {
    c.alloc[i] = &emptymspan
  }
  c.nextSample = nextSample()
  return c
}

mspan数据结构

//go:notinheap
type mspan struct {
  // 前后指针,分别指向了前后的Span
  next *mspan     
  prev *mspan    
  // 当前Span的第一个page的首地址
  startAddr uintptr 
  // 代表当前Span是由多少Page构成的 startAddr*npages*pgae size(8KB)就是当前span分配空间的大小
  npages    uintptr 
​
  manualFreeList gclinkptr // 空闲对象列表// freeindex是0~nelems的位置索引, 标记当前span中下一个空对象索引
  freeindex uintptr
​
  nelems uintptr // 当前span中管理的对象数
​
​
  allocCache uint64  // 从freeindex开始的位标记
  allocBits  *gcBits // 该mspan中对象的位图
  gcmarkBits *gcBits // 该mspan中标记的位图,用于垃圾回收
​
  spanclass   spanClass     // 当前span 对应的spanclass
  .......
}

参考1