Go内存管理

最进公司组织内部分享,我准备了Go语言的内存管理。查了很多资料整理出一幅图,并对知识点做一个整理。

内存空间主要包含两个区域 — 栈区(Stack)和堆区(Heap)。函数调用的参数、返回值以及局部变量都会被分配到栈上(内存逃逸会被分配到堆上),这部分内存会由编译器进行管理;不同编程语言使用不同的方法管理堆区的内存,C++ 等编程语言会由程序员主动new和delete内存,Go 语言会由程序员和编译器共同管理,堆中的对象由内存分配器分配并由垃圾收集器回收。

go-memory-layout

内存管理组件

Go 语言的内存分配器包含内存管理单元(mspan)、线程缓存(mcache)、中心缓存(mcentral)和页堆(mheap)几个重要组件。

所有的 Go 语言程序都会在启动时初始化如上图所示的内存布局,每一个处理器都会被分配一个线程缓存 mcache 用于处理微对象和小对象的分配,它们会持有内存管理单元 mspan。

每个类型的内存管理单元都会管理特定大小的对象,当内存管理单元中不存在空闲对象时,它们会从 mheap 持有的 134 个中心缓存 mcentral 中获取新的内存单元,中心缓存属于全局的堆结构体 mheap,它会从操作系统中申请内存。

内存管理单元

mspan 是 Go 语言内存管理的基本单元,该结构体中包含 next 和 prev 两个字段,它们分别指向了前一个和后一个 mspan

type mspan struct {
    next *mspan
    prev *mspan
    ...
}

串联后的上述结构体会构成如下双向链表,运行时会使用mSpanList 存储双向链表的头结点和尾节点并在线程缓存以及中心缓存中使用。

线程缓存

mcache 是 Go 语言中的线程缓存,它会与线程上的处理器一一绑定,主要用来缓存用户程序申请的微小对象。每一个线程缓存都持有 67 * 2 个 mspan,这些内存管理单元都存储在结构体的 alloc 字段中。

中心缓存

mcentral 是内存分配器的中心缓存,与线程缓存不同,访问中心缓存中的内存管理单元需要使用互斥锁:

type mcentral struct {
    lock      mutex
    spanclass spanClass
    nonempty  mSpanList
    empty     mSpanList
    nmalloc uint64
}

每一个中心缓存都会管理某一类的内存管理单元,它会同时持有两个 mSpanList,分别存储包含空闲对象的列表和不包含空闲对象的链表

页堆

mheap 是内存分配的核心结构体,Go 语言程序只会存在一个全局的结构,而堆上初始化的所有对象都由该结构体统一管理,该结构体中包含两组非常重要的字段,其中一个是全局的中心缓存列表 central,另一个是管理堆区内存区域的 arenas 以及相关字段。

页堆中包含一个长度为 134 的 mcentral 数组,其中 67 个为跨度类需要 scan 的中心缓存,另外的 67 个是 noscan 的中心缓存

小结:

Go 语言的内存分配器实现非常复杂,但从整体来看类似于一个三级缓存,每一级都包含一个数组存这不同大小块内存链表的头指针。每次分配内存是先从最低级找,没有就去上一级取一块。

你可能感兴趣的:(Go内存管理)