Go语言内存管理

220 阅读4分钟

硬件视角下的内存访问:CPU和主存的交互

image.png Control Bus: 控制读还是写

操作系统视角下的内存管理:虚拟内存与物理内存、MMU与页缺失

OS通过MMU利用进程页表将虚拟内存转换为物理地址
若当前虚拟地址不存在对应页表,则MMU出发页缺失硬件中断,触发CPU中断处理逻辑加载页到内存并更新进程页表 image.png

image.png 关键点:OS每次至少分配一页物理内存给进程(4K)

进程视角下的内存空间

image.png Text Segment: 代码段
Data Segment: 数据段,已初始化全局数据&static变量&只读数据
Bss Segment: 未初始化数据段
Stack: 栈,用于存放函数栈帧
Heap: 堆,用户态代码动态管理内存
Dynamic Lib and other Mapping: 即Memory Mapping Segment, 内存映射段,用户进程动态链接库加载与用户态代码动态管理内存,也可视为堆的一部分

内存管理系统调用

  • brk 操作brk指针,brk指针上移,可用内存增加,brk指针下移,可用内存减少
  • mmap/munmap 在内存映射段分配/释放一块儿内存,通常用户态进程申请管理大块儿内存时会使用

用户态内存管理

通过系统调用管理堆内存的申请与释放
关键目标:避免内存碎片&提高进程使用内存效率
核心思想:申请冗余内存资源,空间换时间

tcmalloc

内存管理逻辑 image.png 核心思路:基于系统调用,在用户进程与操作系统之间维护三层内存缓存,避免内存碎片,提高内存使用效率

  • ThreadCache 通过线程级变量为每个线程维护线程内存缓存,本质上核心是一个 FreeList Array
    Array的每一个元素是一个链表,负责维护对应尺度的空闲内存块
    从ThreadCache中获取释放内存只需操作指针,不需要竞争互斥锁

  • CentralCache 多线程共享的中心内存缓存,本质上核心也是FreeList Array
    ThreadCache没有对应尺度的空闲空间,则从CentralCache获取一堆空闲块,若CentralCache也没有内存,则从PageHeap获取
    访问需要获取锁

  • PageHeap 页级内存池,以page为单位向操作系统获取内存
    不同尺度的span(包含多个page)通过不同的链表维护

Go内存管理

Go语言内存管理逻辑同tcmalloc一致,都是通过线程缓存+中心缓存+页级内存池管理内存

基于Go runtime的进程内存视图

Go在程序启动的时候,会先向操作系统申请一块内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理

image.png arena是Go进程动态管理的内存空间,即堆区;spans/bitmap存放一些垃圾回收相关的数据

内存管理基础结构 - mspan

同tcmalloc相比,Go 并非通过FreeList Array管理内存,而是通过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表中的对象大小,也即块大小
}

image.png 每个mspan,针对某个尺度(elemsize),将从startAddar的npages页内存管理起来,划分为 nelems + allocCount 个内存块,供上层用户使用

线程缓存 - mcache

每个Go协程都有自己绑定的mcache

type mcache struct {
	alloc [67*2]*mspan // 按class分组的mspan列表
}

image.png 每个mcache具有67个尺度的mspan链表,依据是否包含指针,分为scan,noscan两类,分类是为了提高GC性能

中心缓存 - mcentral

Go语言维护多个中心缓存mcentral,每个mcentral负责维护某一个尺度内存块的中心缓存

type mcentral struct {
	lock      mutex     //互斥锁
	spanclass spanClass // 内存块尺度
	nonempty  mSpanList // non-empty 指还有空闲块的span列表
	empty     mSpanList // 指没有空闲块的span列表
	nmalloc uint64      // 已累计分配的对象个数
}

image.png

页级内存池 - 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
	}
}

Go实质上就是通过一个全局的mheap管理内存

image.png