这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战
内存空间主要包括两个部分:栈、堆
发生函数调用进行参数传递、返回值传递、局部变量大多分配在栈上,由编译器管理
存储工程师申请的对象等,一般放在堆上,由用户、编译器共同管理
Go内存管理过程
三个组件:用户程序(Mutator)、分配器(Allocator)、收集器(Collector)
分配器
- 内存分配器:用户程序申请内存时,负责申请新的内存(从堆中初始化内存)
分配方法
- 线性分配器
- 只有一个指针
- 指向剩余的大量空余空间
- 分配空间时,指针往后移动
- 单独使用线性分配器没有办法重用已被回收的内存
- 需要与垃圾回收算法配合使用
- 标记压缩
- 复制内存
- 分代回收
- 可以整理内存碎片,调高内存利用率
- 需要与垃圾回收算法配合使用
- 只有一个指针
- 空闲链表分配器
- 通过链表的形式将空闲内存连接起来
- 申请内存遍历所有的内存块,O(n)
- 如何选择使用的内存块
- 首次适应:第一个满足的大小要求的内存块
- 循环首次遍历:从上一次遍历的地方开始首次适应
- 最优适应:遍历整个链表,选择最优解
- 隔离适应:分割成多个链表,每个链表的内存块大小不一样,但是同链表上内存块大小一致,申请时先选择链表,在选择链表上的内存块
- Go使用的是类似第四种策略
-
- 分级分配
-
- 线程缓存分配:据说币malloc还要快
- Go借鉴了线程缓存分配
- 多级缓存,根据大小分类,根据类别实施
- 大小分类:大小会影响分配内存过程与开销,分开处理可以提高性能
- 多级缓存:引入了线程缓存、中心缓存、页堆
- 线程缓存:分配小对象内存,属于每个独立线程,不存在锁等,能够提高部分性能
- 中心缓存:分配小对象内存,线程缓存不满足需求的时候(如多线程),会使用中心缓存
- 页堆:分配超过32KB以上对象,分配大内存
- 通过链表的形式将空闲内存连接起来
虚拟内存布局
- Go1.11以前使用的是线性内存,这里不做过多讨论
- Go1.11后使用稀疏内存
- 解决堆大小上限问题
- 解决C、Go混合使用内存带来的冲突问题
- 内存失去连续性,管理更加复杂
- 二维稀疏内存
- 通过二维runtime.heapArena数组管理内存
-
- heapArena中有指针指向对应管理的内存位置
- 每一个管理64MB
- 不同平台上的二维数组大小不一样
- 地址空间
- 所有的内存都是由实际操作系统中申请获得的,由实际内存地址空间映射到Go的管理空间,也就是自己做了一层抽象
- 有四种不同的状态来表示对应地址的状态
- 同时go也实现了操作系统内存操作的方法
内存管理组件
- Go在启动的时候会为每个处理器都分配一个线程缓存
- 线程缓存(mcache),mcache存会管理特定大小的对象,持有mspan(内存管理单元)
- mspan没有空闲对象后从mheap持有的134个中心缓存mcentral中获取新的内存单元
- 中心缓存属于全局的堆结构体mheap
- mheap会从操作系统中申请内存
接下来从小到大进行介绍
内存管理单元
mspan(内存管理单元)是最基本的内存管理单元
mspan之间会构建成双向链表
type mspan struct {
next *mspan
prev *mspan
startAddr uintptr // 起始地址
npages uintptr // 页数,每页大小为8KB,为操作系统内存页的整数倍
freeindex uintptr // 扫描页中空闲对象的初始索引
allocBits *gcBits // 内存占用情况
gcmarkBits *gcBits // 内存回收情况
allocCache uint64 // allocBits补码,快速查找未使用内存
state mSpanStateBox // 描述当前mspan的状态,状态转换为原子性的,避免垃圾回收的线程竞争问题
state mSpanStateBox // 跨度类,决定内存存储单元中存储对象的大小、个数
}
- 内存不足的时候,以页为单位向堆申请内存
- 用户程序或线程向mspan申请内存的时候,可以通过allocCache快速查看未分配的内存
- 无空闲内存的时候,调用refill更新内存管理单元
- Go中有67中跨度类,每种跨度类的对象大小、页数等都不一样
线程缓存
即mcache,与线程(M)绑定,在alloc字段中有68*2个mspan
初始化
- 会从mheap中获取mspan结构体,获取的mspan结构体中都是空的占位符
微分配器
专门处理16字节以下的对象分配(非指针类型)
中心缓存
即mcentral,不独属于某个线程,因此访问需要上锁(悲观锁)
- 每个中心缓存只管理某个跨度类
- 持有两个spanSet,分别管理空闲以及忙碌的mspan
- 线程缓存也是通过中心缓存获取mspan的
- 扩容,从mheap中获取对应跨度类的新mspan
页堆
即mheap
- 为全局变量
- 统一管理堆上分配的对象
- 包含一个mcentral数组
- 包含heapArena字段,进行二维矩阵内存的管理
- 每个管理64M空间
内存分配
微对象
微型分配器(位于线程缓存上)-》线程缓存-》中心缓存-》堆
- 小于16字节
微分配器:
- 主要用于分配较小字符串、逃逸的临时变量
- 对象不可是指针类型
- 一个内存块管理多个微对象
- 所有对象可回收时,整个内存才可被回收
小对象
线程缓存-》中心缓存-》堆
- 小于16字节的指针对象
- 16~32768字节的对象
大对象
堆
- 会去计算需要多少npages,然后返回一个只能容纳一个该跨度类的mspan,管理对应数量的npages