文章目录
内存管理
用户程序(Mutator)
分配器(Allocator)
收集器(Collector)
内存分配器
常见的内存分配方法
线性分配器(Sequential Allocator,Bump Allocator)
在内存中维护一个指向内存特定位置的指针
按顺序分配内存
- 缺点
应用释放内存后无法重新利用
标记压缩(Mark-Compact)、复制回收(Copying GC)和分代回收(Generational GC)等算法,它们可以通过拷贝的方式整理存活对象的碎片,将空闲内存定期合并
空闲链表分配器(Free-List Allocator)
内部会维护一个类似链表的数据结构,记录空闲内存
程序申请内存时,空闲链表分配器会依次遍历空闲的内存块,找到合适的内存块,然后申请资源并修改链表
-
缺点
遍历,时间复杂度是 𝑂(𝑛) -
内存块选择策略:
首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块;
循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块;
最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
隔离适应(Segregated-Fit)— 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块;
线程缓存分配(Thread-Caching Malloc,TCMalloc)
核心理念:使用多级缓存将对象根据大小分类,并按照类别实施不同的分配策略。
go的内存分配策略
go的策略类似 空闲链表分配器(隔离适应) + TCMalloc
内存块选择策略
将内存分割成4个链表,分别由 4、8、16、32 字节的内存块组成的,程序申请内存时 找到满足条件的空闲内存块
对象分类策略
运行时绝大多数对象的大小都在 32KB 以下
根据对象的大小将对象分成微对象、小对象和大对象三种分别处理
类别 | 大小 | 分配方法 |
---|---|---|
微对象 | (0, 16B) | 先使用微型分配器,再依次尝试线程缓存、中心缓存和堆分配内存 |
小对象 | [16B, 32KB] | 依次尝试使用线程缓存、中心缓存和堆分配内存 |
大对象 | (32KB, +∞) | 直接在堆上分配内存 |
多级缓存
将内存分成不同的级别分别管理
引入线程缓存(Thread Cache)、中心缓存(Central Cache)和页堆(Page Heap)三个组件分级管理内存
垃圾收集器
常见的垃圾收集机制
手动、自动
C、C++ 以及 Rust 等编程语言使用手动的方式管理内存,工程师需要主动申请或者释放内存
Python、Ruby、Java 和 Go 等语言使用自动的内存管理系统,一般都是垃圾收集机制
暂停程序(Stop the world,STW)
当程序的内存占用达到一定阈值时,整个应用程序就会全部暂停,
垃圾收集器会扫描已经分配的所有对象并回收不再使用的内存空间,
当这个过程结束后,用户程序才可以继续执行
垃圾收集算法
标记清除(Mark-Sweep)
长时间 STW
三色抽象
白色对象 — 潜在的垃圾,其内存可能会被垃圾收集器回收;
黑色对象 — 活跃的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象;
灰色对象 — 活跃的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;
- 标记阶段流程
- 全都是白
- 根对象>灰
- 根对象>黑,取根对象的子节点对象 作为新的根对象
- 重复2-3
当三色的标记清除的标记阶段结束之后,只能看到黑色的存活对象以及白色的垃圾对象,垃圾收集器可以回收这些白色的垃圾
- 缺点
悬挂指针:标记阶段 如果程序正常运行 使用了白色对象,最后造成错误回收
屏障技术
cpu乱序执行指令以最大化性能。
屏障技术 保证内存操作的顺序性。保证三色不变性
并发 增量
go垃圾收集演进过程
v1.0 — 完全串行的标记和清除过程,需要暂停整个程序;
v1.1 — 在多核主机并行执行垃圾收集的标记和清除阶段;
v1.3 — 运行时基于只有指针类型的值包含指针的假设增加了对栈内存的精确扫描支持,实现了真正精确的垃圾收集;
将 unsafe.Pointer 类型转换成整数类型的值认定为不合法的,可能会造成悬挂指针等严重问题;
v1.5 — 实现了基于三色标记清扫的并发垃圾收集器;
大幅度降低垃圾收集的延迟从几百 ms 降低至 10ms 以下;
计算垃圾收集启动的合适时间并通过并发加速垃圾收集的过程;
v1.6 — 实现了去中心化的垃圾收集协调器;
基于显式的状态机使得任意 Goroutine 都能触发垃圾收集的状态迁移;
使用密集的位图替代空闲链表表示的堆内存,降低清除阶段的 CPU 占用;
v1.7 — 通过并行栈收缩将垃圾收集的时间缩短至 2ms 以内;
v1.8 — 使用混合写屏障将垃圾收集的时间缩短至 0.5ms 以内;
v1.9 — 彻底移除暂停程序的重新扫描栈的过程;
v1.10 — 更新了垃圾收集调频器(Pacer)的实现,分离软硬堆大小的目标;
v1.12 — 使用新的标记终止算法简化垃圾收集器的几个阶段;
v1.13 — 通过新的 Scavenger 解决瞬时内存占用过高的应用程序向操作系统归还内存的问题;
v1.14 — 使用全新的页分配器优化内存分配的速度;