go内存管理

golang内存管理

前言

golang实现了自己的内存管理,在研究切片扩容的时候,有一步roundsize调整内存大小方法,随后对golang中的内存管理深入学习了下,固记录。

概念

在程序启动的时候,golang会预先向处理器申请,如下虚拟地址空间(并没有真正的申请),主要将其规划给spans、bitmap、arena三部分

+-----------------------------------------------------------+
|   spans	|		bitmap		|			arena		    |
+-----------------------------------------------------------+

arena:就是说的内存堆区,真正分配内存的地方,大小为512G,内部由多个page分割,page是内存存储的基本单元(8KB大小)

spans:存放多个*span,span是内存管理的基本单元,后文提及。需要确保每个span都能够指向不同的page位置,所以spans的大小为(512G/8KB)*每个指针大小8B=512M

bitmap:保存gc相关的位图信息

object:代码中等待分配的具体对象,在arena区域分配内存(一个object可能比page大也可能比page小)

策略

golang中的内存分配策略主要参考了C语言中的tcmalloc,将存储对象(object)分为大小不同的多种,分别进行管理,减少内存碎片的问题,在golang中主要划分为67种类别class

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

class:类别

bytes/obj:该class类别每个对象的大小

bytes/span:该class类别span的总大小

objects:可以存放的对象数 (bytes/span)/(bytes/obj)

max waste:最大浪费空间,example:大小为1B的对象为其分配8B的内存空间,浪费7/8=87.5%

bytes/obj满足8*2n的关系,至于为什么是这个关系?设计考虑到整体max waste需小于某个值,在golang中这个值为多少没有具体了解

span

span是内存管理的基本单元,由1个或者多个page组成,page个数在go中也是写死的

// runtime/sizeclasses.go	
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
// runtime/mheap.go
type mspan struct {
	next *mspan
	prev *mspan
	list *mSpanList

	startAddr uintptr // mspan在areana中的开始地址
	npages    uintptr // span的page数

	freeindex uintptr // 下一个可使用object的索引
	nelems    uintptr // span中object的个数

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

	allocBits  uintptr //分配位图,每一位代表一个块是否已分配
	allocCount uint16  // 已分配块的个数
}

span在源码中的数据结构对应mspan,只写了一部分比较重要的字段,后文源码展示同理。

可以看出mspan是一个双向的链表,保存其在arena中开始位置startAddr、分配页数npages、span中存放的object信息等…

spans中*span指针就指向该结构体

基本单元

内存管理主要通过mcachemcentralmheap进行配合

mcache
// runtime/mcache.go
type mcache struct {

	// 每种类别是一个链表
	// class0 scan class0 noscan
	//	 | 			 |
	// 	 |			 |
	// class0 scan class0 noscan

	// 每一个mcache有134种mspan(67个指针供指针)
	// 每一个mspan可以分配多个object
	alloc [67*2]*mspan
}

在这里插入图片描述

mcache就是管理mspan的基本单元,alloc字段为长度67*2的的*mspan数组,大小为classsize的两倍,原因是go在处理内存时将指针对象和非指针对象区分处理了,对于包含指针的对象需要进行扫描操作scan,不包含指针的对象不需要进行多余的扫描操作noscan,也是一种性能优化方案。每个class对应一个span链表,需要注意的是该链表对应的都是该class的span,如注释中那样。

在程序运行时每个线程P会与一个mcache进行绑定(GMP调度模型),所以mcache不存在锁问题,在P运行初始是不存在任何span的

mcentral

mcentral是内存分配的中转站(central),每个class对应一个mcentral。当某个mcache无内存可用时到相应class的mcentral中获取,所以mcentral是被多个线程共享的,需要锁

// runtime/mcentral.go
type mcentral struct {
	// 多个goroutine共享的拿mspan所以需要锁
	lock      sync.Mutex
	spanclass spanClass

	// 维护两个双向链表
	// 当有mcache申请一个mspan时
	// 遍历链表查找可利用的mspan
	// 当没有mspan可用时,mcentral向mheap申请并返回给mcache
	nonempty mSpanList // 还有空闲对象或者已经被mcache缓存的mspan
	empty    mSpanList // 没有空闲对象的mspan联表
}

mcentral维护两个双向链表nonempty还有空闲对象的span链表和empty没有空闲对象的span链表。

mheap

mheap.central字段保存67*2个mcentral,程序运行开始预先分配的bitmapspansarena就是通过mheap进行管理,mheap作为上层结果管理所有的内存空间

// runtime/mheap.go
type mheap struct {
	// mheap对所有线程共享,固需锁
	lock sync.Mutex

	allspans []*mspan // 所有mspan

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

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

	// heap由134个mcentral组成,67个无指针mcentral
	// 67个有指针mcentral
	// 每个class 一个mcentral
	central [134]struct {
		mcentral mcentral
	}
}

每个class类别对应一个mcentral,一共有67*2(scan和noscan两种)种class也就是134种mcentral

mheap对所有线程共享固需加锁

内存分配流程

前文提及的对象大小划分图种只列出了66种,go种将对象大小划分为67种,最后一种为class为0的类别,bytes/obj>32KB的对象,此种对象称为大对象,直接由mheap进行分配、另外就是小于16B的对象称为tiny对象,go会使用tinyalloc分配器进行分配,本文着重讲[16B,32KB]的对象的内存分配管理

当线程需要获取大小为N的内存时:

  1. 判断N大小属于两个class类别区间,向上取整获取需为其分配的内存空间大小,example需要为bool类型的1B大小分配内存空间向上取整为8B(所以会有一部分内存的浪费)
  2. 获取当前线程P绑定的mcache
  3. 通过对象大小获取class ID,在mcache.alloc[class]中获取相应class的span链表,判断是否还有可用的object空间
  4. 如果没有可用的span空间则向相应class的mcentral申请span

线程从mcentral获取span:

  1. mcentral加锁
  2. mcentral从nonempty中取出可用span并删除,empty中添加相应span,如果没有可利用的span则向mheap获取
  3. mcentral返回span

mcache拿到可利用span则获取空闲object区域,返回地址

线程将span返还给mcentral过程:

  1. mcentral加锁
  2. 从empty中删除相应span,nonempty中添加相应大小span
  3. mcentral解锁

总结

  1. go程序启动时预先申请一片虚拟地址空间自行管理
  2. 将地址空间分为bitmapspansarena三个区域,三片区域都通过mheap数据结构进行统一管理
  3. spans保存指向mspan的指针,mspan内存管理的基本单位,主要以链表方式展现。bitmap保存gc相关的位图信息,比如哪些被gc扫描过,哪些没有。arena是就是保存对象的地址堆区
  4. span由一个或多个页page组成,在go中已经被定死了,真正存储的对象为object,被分为大小不同的67种
  5. mcache与线程绑定,线程需要获取内存时首先从mcache获取,不存在锁
  6. mcentral供多个线程共享span,当mcache没有可用span时从mcentral获取

参考资料

[图解go内存分配]:https://juejin.cn/post/6844903795739082760

[Go’s Memory Allocator - Overview]:https://andrestc.com/post/go-memory-allocation-pt1/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值