硬件视角下的内存访问:CPU和主存的交互
Control Bus: 控制读还是写
操作系统视角下的内存管理:虚拟内存与物理内存、MMU与页缺失
OS通过MMU利用进程页表将虚拟内存转换为物理地址
若当前虚拟地址不存在对应页表,则MMU出发页缺失硬件中断,触发CPU中断处理逻辑加载页到内存并更新进程页表
关键点:OS每次至少分配一页物理内存给进程(4K)
进程视角下的内存空间
Text Segment: 代码段
Data Segment: 数据段,已初始化全局数据&static变量&只读数据
Bss Segment: 未初始化数据段
Stack: 栈,用于存放函数栈帧
Heap: 堆,用户态代码动态管理内存
Dynamic Lib and other Mapping: 即Memory Mapping Segment, 内存映射段,用户进程动态链接库加载与用户态代码动态管理内存,也可视为堆的一部分
内存管理系统调用
- brk 操作brk指针,brk指针上移,可用内存增加,brk指针下移,可用内存减少
- mmap/munmap 在内存映射段分配/释放一块儿内存,通常用户态进程申请管理大块儿内存时会使用
用户态内存管理
通过系统调用管理堆内存的申请与释放
关键目标:避免内存碎片&提高进程使用内存效率
核心思想:申请冗余内存资源,空间换时间
tcmalloc
内存管理逻辑
核心思路:基于系统调用,在用户进程与操作系统之间维护三层内存缓存,避免内存碎片,提高内存使用效率
-
ThreadCache 通过线程级变量为每个线程维护线程内存缓存,本质上核心是一个 FreeList Array
Array的每一个元素是一个链表,负责维护对应尺度的空闲内存块
从ThreadCache中获取释放内存只需操作指针,不需要竞争互斥锁 -
CentralCache 多线程共享的中心内存缓存,本质上核心也是FreeList Array
ThreadCache没有对应尺度的空闲空间,则从CentralCache获取一堆空闲块,若CentralCache也没有内存,则从PageHeap获取
访问需要获取锁 -
PageHeap 页级内存池,以page为单位向操作系统获取内存
不同尺度的span(包含多个page)通过不同的链表维护
Go内存管理
Go语言内存管理逻辑同tcmalloc一致,都是通过线程缓存+中心缓存+页级内存池管理内存
基于Go runtime的进程内存视图
Go在程序启动的时候,会先向操作系统申请一块内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理
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表中的对象大小,也即块大小
}
每个mspan,针对某个尺度(elemsize),将从startAddar的npages页内存管理起来,划分为 nelems + allocCount 个内存块,供上层用户使用
线程缓存 - mcache
每个Go协程都有自己绑定的mcache
type mcache struct {
alloc [67*2]*mspan // 按class分组的mspan列表
}
每个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 // 已累计分配的对象个数
}
页级内存池 - 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管理内存