本文对Go堆内存管理原理进行了介绍.
func main() {
f
}
1.引入
虚拟内存
Go 程序是一个进程, 进程所面对的都是虚拟内存.操作系统通过 MMU 将虚拟内存映射到真实的物理内存.
虚拟内存的好处:
- 内存安全: 本进程的内存不会被其他进程访问.
- 对于进程而言,内存是固定且连续的,方便程序设计与内存管理
内存分配方式
线性分配
在内存中维护一个指向内存特定位置的指针,如果用户程序向分配器申请内存,分配器只需要检查剩余的空闲内存、返回分配的内存区域并修改指针在内存中的位置(指针碰撞).

特点: 速度快,但无法在内存被释放时重用内存.基于上述,该分配算法需要配合适合的垃圾回收算法配合使用,比如定期通过拷贝整理存活对象的碎片,规整内存.(Java 年轻代复制回收算法)
空闲链表分配
维护类似链表的数据结构,用于标记空闲的内存.申请内存时,遍历链表找到一个合适的内存用于分配. 寻找策略: 首次适应,最优适应,隔离适应等. 重点介绍隔离适应(与 Go 语言的方式相似): 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块;

TcMalloc
TCMalloc 是 Thread Cache Malloc 的简称,是由 Google 开源(https://github.com/google/tcmalloc)的一种对抗内存碎片化的内存分配器. 主要涉及两个原理:
-
将内存划分为一个个特定大小的空闲块,称为 span,同时维护同种大小内存块的空闲链表.实现快速找到空闲内存与减少内存碎片化.
-
将对象区分为小中大三种类型,抽象出线程缓存,中心缓存,页堆等内存管理结构,提升内存分配速度.
-
小对象直接在线程缓存中进行无锁化内存分配, 中大对象通过页堆中进行分配,需要加锁
-
内存的总体分配流程是: 线程缓存 -> 中心缓存 -> 页堆 -> 操作系统申请
-

Go 对象分类
Go 语言根据大小与是否需要扫描将对象分为 3 类:
| 对象类型 | Size | 是否需要扫描 |
|---|---|---|
| 微对象 | [0B,16B] | 否 |
| 小对象 | [0B,32KB] | 是 |
| 小对象 | [16B,32KB] | 否 |
| 大对象 | [16B,32KB] | 否 |
不同的对象具有不同的内存申请分配策略.
页 page
操作系统以页为单位进行管理.Go 内存管理有同样的概念. Linux 的页默认大小为 4KB,而 Go 中的页大小为 8KB