本文对Go堆内存管理原理进行了介绍.

func main() {
	f
}

1.引入

虚拟内存

image-20230723221732636 Go 程序是一个进程, 进程所面对的都是虚拟内存.操作系统通过 MMU 将虚拟内存映射到真实的物理内存. 虚拟内存的好处:

  • 内存安全: 本进程的内存不会被其他进程访问.
  • 对于进程而言,内存是固定且连续的,方便程序设计与内存管理

内存分配方式

线性分配

在内存中维护一个指向内存特定位置的指针,如果用户程序向分配器申请内存,分配器只需要检查剩余的空闲内存、返回分配的内存区域并修改指针在内存中的位置(指针碰撞).

image-20230723221816378

特点: 速度快,但无法在内存被释放时重用内存.基于上述,该分配算法需要配合适合的垃圾回收算法配合使用,比如定期通过拷贝整理存活对象的碎片,规整内存.(Java 年轻代复制回收算法)

空闲链表分配

维护类似链表的数据结构,用于标记空闲的内存.申请内存时,遍历链表找到一个合适的内存用于分配. 寻找策略: 首次适应,最优适应,隔离适应等. 重点介绍隔离适应(与 Go 语言的方式相似): 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块;

image-20230723221834435

TcMalloc

TCMalloc 是 Thread Cache Malloc 的简称,是由 Google 开源(https://github.com/google/tcmalloc)的一种对抗内存碎片化的内存分配器. 主要涉及两个原理:

  • 将内存划分为一个个特定大小的空闲块,称为 span,同时维护同种大小内存块的空闲链表.实现快速找到空闲内存与减少内存碎片化.

  • 将对象区分为小中大三种类型,抽象出线程缓存,中心缓存,页堆等内存管理结构,提升内存分配速度.

    • 小对象直接在线程缓存中进行无锁化内存分配, 中大对象通过页堆中进行分配,需要加锁

    • 内存的总体分配流程是: 线程缓存 -> 中心缓存 -> 页堆 -> 操作系统申请

image-20230723221853342

Go 对象分类

Go 语言根据大小与是否需要扫描将对象分为 3 类:

对象类型 Size 是否需要扫描
微对象 [0B,16B]
小对象 [0B,32KB]
小对象 [16B,32KB]
大对象 [16B,32KB]

不同的对象具有不同的内存申请分配策略.

页 page

操作系统以页为单位进行管理.Go 内存管理有同样的概念. Linux 的页默认大小为 4KB,而 Go 中的页大小为 8KB