Golang -内存管理

内存分配

Golang在程序启动时预先向操作系统申请内存,包括:

在这里插入图片描述

  1. arena即堆区,应用中需要的内存都从这里分配,大小为512G
    • 为了方便内存的管理,arena区内存被分成一个个的page,页, 每个页大小为8KB, 整个arena共512G / 8K = 64M个页
  2. bitmap即位图区,存放内存管理中所有的位图信息
    • 16G?
  3. spans即所有已创建的span的指针列表(下文会介绍span的概念和功能)
    • 每个Span指向一个page,故spans大小为64M * 8B = 512MB; (一个指针8个bytes)

Span

Span作为一个数据实体,对内存Class进行管理

Class

Go为了方便内存管理,根据内存块block的大小,分为不同的class

// GO1.14.4 src/runtime/sizeclasses.go
// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         32        8192      256           0     46.88%
//     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%
...
//    66      32768       32768        1           0     12.50%

class=10为例:

  1. class表示内存块类型ID: 10
  2. bytes/obj表示每个内存块大小: 144个字节
  3. bytes/span表示对应span的大小:8192个字节,span以内存页为单位,一页大小8KB
  4. objects该span拥有内存块个数: 8192 / 144 ~= 56
  5. waste浪费了多少字节: 整除后剩余128个字节

Span对Class的抽象

每一个Span数据结构,即是对一个Class类型的抽象

// GO1.14.4 src/runtime/mheap.go
type mspan struct {
	spanclass   spanClass  // 当前Span对应Class的编号,如10
	startAddr uintptr // span对应的起始地址
	npages    uintptr // span包含多少个Page(固定值,如class=10时,npages=1)
	nelems uintptr    // span中包含多少个块
	allocCount  uint16     // 已分配块个数
	elemsize    uintptr    // 块大小
	
	allocBits  *gcBits    // 已分配块的位图;gcBits是一个指针,指向该span各个块使用位图的指针
	gcmarkBits *gcBits    // 用于GC

	next *mspan     // 在span列表中下一个的span
	prev *mspan     // 在span列表中前一个的Span
	...
}

已Class = 10为例:

Span10 = mspan{
	spanclass: 10,
	startAddr: pointer, // 一个指向arena的指针
	npages: 1,
	nelems: 56,
	allocCount: 22, // 假设有22个块已分配
	elemsize: 144,
	...
}

可以看出,Span对应某个特定ClassID,可以提供该ClassID对应块大小内存的分配

Central

Central用于全局管理Span,当线程中内存不足时,可以想Central申请Span

Central的数据结构

// runtime/mcentral.go
type mcentral struct {
	lock      mutex   // 并发锁,当多个线程同时来申请资源时需要加锁
	spanclass spanClass  // classID
	nonempty  mSpanList // objects非空,可申请的Span列表
	empty     mSpanList  // 

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

// 链表的方式记录span列表
type mSpanList struct {
	first *mspan
	last  *mspan
}

可以看到,Central统一管理指定ClassID的Span列表

线程向Central申请Span步骤

  1. 加锁
  2. nonempty中获取一个可用Span,并将其从链表中删除
  3. 将取出的Span添加进empty列表,表示该Span已被申请
  4. 将Span返回给线程
  5. 解锁

Cache

为了解决多线程向Central申请Span的并发问题,Golang提供了Cache机制,将Span缓存到Processor中,

Cache的数据结构

// Per-thread (in Go, per-P) cache for small objects.
// No locking needed because it is per-thread (per-P).
// 每个线程(或者说每个P)独有的内存缓存池
type mcache struct {
	alloc [numSpanClasses]*mspan // numSpanClasses = 134, 可以根据ClassID索引得到可用的span结构
}
  1. Cache是线程/P独有的内存缓存,解决多线程同时向Central申请内存加锁的问题
  2. Cache维护Span列表,可通过ClassID作为索引快速获取可用Span
    • 列表大小是67 * 2, 每个ClassID相邻两个Span,一个用于GC

Heap

上面提到的SpanCentral都是指定某一个ClassID的数据结构,而Heap则是管理所有ClassID的全部内存

Heap的数据结构

type mheap struct {
	lock      mutex
	allspans []*mspan // 所有已创建的Span列表
	
	// 维护所有Central的映射
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}
	
	// heap当前正在使用的arena,包括起止地址
	curArena struct {
		base, end uintptr
	}  
	...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值