1.内存分配模型

:分配器会负责从堆中初始化相应的内存区域
比较灵活,可以按需分配,但是容易造成内存泄漏,悬挂指针
:函数调用的参数、返回值以及局部变量大都会被分配到栈上,这部分内存会由编译器进行管理
可以进行自动管理,但是大小固定,会发生栈溢出

1.设计原理

内存分配方式
1.线性分配
2.空闲链表分配
首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块;
循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块;
最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
3.隔离适应分配
将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块
image.png
4.多级线程缓存分配
Go 语言的内存分配器会根据申请分配的内存大小选择不同的处理逻辑,运行时根据对象的大小将对象分成微对象、小对象和大对象三种:

类别 大小
微对象 (0, 16B)
小对象 [16B, 32KB]
大对象 (32KB, +∞)

除了会区别对待大小不同的对象,还会将内存分成不同的级别分别管理,Go 运行时分配器都会引入线程缓存(Thread Cache)、中心缓存(Central Cache)和页堆(Page Heap)三个组件分级管理内存

image.png
spans 区域存储了指向内存管理单元的指针,每个内存单元会管理几页的内存空间,每页大小为8KB;
bitmap 用于标识 arena区域中的哪些地址保存了对象,位图中的每个字节都会表示堆区中的32字节是否空闲;
arena 区域是真正的堆区,运行时会将 8KB 看做一页,这些内存页中存储了所有在堆上初始化的对象;
image.png
稀疏的内存布局不仅能移除堆大小的上限,还能解决 C 和 Go 混合使用时的地址空间冲突问题

2.内存管理

image.png

mspan是内存管理的基本单元,负责一个固定大小的区域,根据大小分不同的class
mcache是线程缓存,每个处理器负责一个mcache,持有136个mspan,每一个处理器都会分配一个线程缓存mcache用于处理微对象和小对象的分配,访问线程缓存不需要锁
mcentral是中心缓存,访问中心缓存中的内存管理单元需要使用互斥锁,用于处理特定class的msapn
mheap是页堆,持有67*2个mcentral,Go会将其作为全局变量存储,而堆上初始化的所有对象都由该结构体统一管理

2.垃圾回收模型

垃圾回收的方式

1.手动清理,c,c++,rust
2.自动清理
引用计数法:python
可达性分析:java,go

go垃圾回收算法

1.标记清除

整个过程需要标记对象的存活状态,用户程序在垃圾收集的过程中也不能执行,stw(stop the world)太久

2.三色标级

白色对象 — 潜在的垃圾,其内存可能会被垃圾收集器回收;
黑色对象 — 活跃的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象;
灰色对象 — 活跃的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;
image.png
不可以并发或者增量执行的,假设在标记阶段发生了指针的变化,可能会导致对象丢失,例如上图。所以三色标记的实现仍然需要 STW

3.屏障技术

满足两点可以保证不存在对象丢失
弱三色不变式:所有被黑色对象引用的白色对象都处于灰色保护状态(直接或间接从灰色对象可达)
强三色不变式:不存在黑色对象到白色对象的指针

插入写屏障拦截将白色指针插入黑色对象的操作,标记其对应对象为灰色状态,这样就不存在黑色对象引用白色对象的情况了,满足强三色不变式
Golang对栈上指针的写入添加写屏障的成本很高,所以Go选择仅对堆上的指针插入增加写屏障,这样就会出现在扫描结束后,栈上仍存在引用白色对象的情况,这时的栈是灰色的,不满足三色不变式,所以需要对栈进行重新扫描使其变黑,完成剩余对象的标记,这个过程需要STW。这期间会将所有goroutine挂起,当有大量应用程序时,时间可能会达到10~100ms

删除写屏障也是拦截写操作的,在老对象的引用被删除时,将白色的老对象涂成灰色,这样删除写屏障就可以保证弱三色不变性,老对象引用的下游对象一定可以被灰色对象引用

混合写屏障是上述两种的结合,go1.8后采用此方式

总结:
(1)将栈上的对象全部标记为黑色
(2)被删除的对象指向的对象标记为灰色
(3)被添加的对象标记为灰色

4.并发增量

增量式(Incremental)的垃圾收集是减少程序最长暂停时间的一种方案,它可以将原本时间较长的暂停时间切分成多个更小的 GC 时间片,虽然从垃圾收集开始到结束的时间更长了,但是这也减少了应用程序暂停的最大时间

并发(Concurrent)的垃圾收集不仅能够减少程序的最长暂停时间,还能减少整个垃圾收集阶段的时间,通过开启读写屏障、利用多核优势与用户程序并行执行,并发垃圾收集器确实能够减少垃圾收集对应用程序的影响

3.栈内存管理

寄存器是CP中的稀缺资源,它的存储能力非常有限,但是能提供最快的读写速度,充分利用寄存器的速度可以构建高性能的应用程序。寄存器在物理机上非常有限,然而栈区的操作会使用到两个以上的寄存器,这足以说明栈内存在应用程序的重要性。
栈寄存器是 CPU 寄存器中的一种,它的主要作用是跟踪函数的调用栈,Go 语言的汇编代码包含 BP 和 SP 两个栈寄存器,它们分别存储了栈的基址指针和栈顶的地址,栈内存与函数调用的关系非常紧密,我们在函数调用一节中曾经介绍过栈区,BP 和 SP 之间的内存就是当前函数的调用栈。
image.png