Go语言内存分配源码分析学习笔记
大家好,我是V 哥。GO GO GO,今天来说一说Go语言内存分配问题,Go语言内存分配的源码主要集中在runtime
包中,它实现了Go语言的内存管理,包括初始化、分配、回收和释放等。下面来对这些过程详细分析一下,先赞后看,绝不摆烂:
1. 内存管理初始化
源码位置: runtime/malloc.go
关键点:
mheap
初始化:mheap
是整个Go运行时的核心内存分配结构,用于管理大块内存。- 初始化时,Go会从操作系统中获取一大块内存作为堆空间,通过
sysAlloc
分配给mheap
。
func mallocinit() {
mheap_.init() // 初始化全局mheap_
}
mcache
初始化:- 每个P(逻辑处理器)有一个
mcache
,用来缓存小块内存分配,减少锁竞争。 mcache
由mheap
分配,存储小块内存(≤32KB)。
- 每个P(逻辑处理器)有一个
func allocmcache() *mcache {
c := new(mcache)
c.refill() // 预填充小内存块
return c
}
2. 内存分配
Go的内存分配分为以下几种场景:
2.1 小对象分配(≤32KB)
- 使用
mcache
中的内存。 mcache
按大小类(class)分配,这些类通过sizeclasses
数组定义。- 分配时,调用
mcache.alloc
,如果mcache中没有可用的内存块,会从mheap
中拉取。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if size <= maxSmallSize {
// 小对象分配
c := getmcache() // 获取当前P的mcache
s := c.alloc(size, needzero)
return s
}
}
2.2 大对象分配(>32KB)
- 直接从
mheap
中分配大块内存。 - 使用
span
(连续内存块)管理这些大块内存。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if size > maxSmallSize {
// 大对象分配
s := mheap_.allocSpan(size)
return s
}
}
3. 垃圾回收(GC)
源码位置: runtime/mgc.go
Go的垃圾回收器使用三色标记清除算法,主要分以下几个阶段:
- 标记阶段:
- 从根对象(全局变量、栈变量、寄存器变量)开始,标记所有可达对象。
- 清除阶段:
- 将未标记的对象回收,释放到
mcache
或mheap
。
- 将未标记的对象回收,释放到
func gcSweep() {
// 遍历所有span,清理未使用的对象
for _, s := range mheap_.spans {
s.sweep()
}
}
4. 内存释放
源码位置: runtime/malloc.go
Go会主动将不再使用的大块内存返还给操作系统,调用sysUnused
或sysFree
实现。
关键点:
- 小对象:
- 释放到
mcache
。 - 如果
mcache
满了,释放到mheap
。
- 释放到
- 大对象:
- 直接释放到
mheap
。 - 如果
mheap
中内存长时间未使用,释放给操作系统。
- 直接释放到
5. 内存分配中的优化机制
5.1 线程本地缓存(mcache)
- 减少全局锁竞争。
- 小对象分配从
mcache
中直接获取。
5.2 内存对齐
- Go保证分配的内存地址按对象大小对齐(如8字节、16字节等),以提高访问效率。
5.3 分配池(Free List)
- 回收的内存会进入Free List,供后续快速分配。
5.4 GC触发条件
- 当堆的增长超过特定比例(默认100%)时触发GC。
Go的内存分配机制结合了现代内存分配的多种优化技术,能够高效地处理并发场景。关键点在于:
- 小对象通过
mcache
优化分配速度。 - 大对象通过
mheap
管理,提高内存利用率。 - 垃圾回收器负责自动清理无用内存,保证程序健壮性。
- 同时,内存释放机制及时将多余内存返还给操作系统,避免浪费。
6. mcache 和 mheap
深入分析 Go 内存管理中核心模块 mcache
和 mheap
的代码实现,可以更好地理解它们的协同工作方式。以下是详细的源码分析:
1. mcache 模块
1.1 mcache 数据结构
mcache
是每个 P (逻辑处理器) 的本地内存缓存,目的是减少对全局堆的锁争用。
它的源码定义在 runtime/mcache.go
。
type mcache struct {
alloc [numSpanClasses]*mspan // 每个 size class 分配一个 span
tiny uintptr // 小对象分配缓存
tinyoffset uintptr // tiny 的当前偏移量
local_nlookup uintptr // 本地分配次数
...
}
字段解释:
alloc
:- 存储分配的 spans,按
size class
分类。 - 每个类的 span 会被重用以分配同类大小的对象。
- 存储分配的 spans,按
tiny
和tinyoffset
:- 用于小对象分配(如
mallocgc
)。
- 用于小对象分配(如
local_nlookup
:- 用于统计本地内存分配的次数。
1.2 mcache 的主要方法
1.2.1 分配内存 (mcache.alloc
)
当分配小对象时,调用 alloc
方法从 mcache
中获取内存:
func (c *mcache) alloc(size uintptr, needzero bool) unsafe.Pointer {
sc := sizeToClass(size) // 根据 size 找到对应的 size class
s := c.alloc[sc]
if s == nil || s.freeindex == s.nelems {
// 当前缓存中没有可用的 span,从 mheap 中获取
s = mheap_.allocSpan(sc)
if s == nil {
throw("out of memory")
}
c.alloc[sc] = s
}
...
return obj
}
工作流程:
- 根据
size
计算size class
。 - 查找对应的
span
:- 如果
span
有空闲块,从freeindex
取一个。 - 如果
span
已满,从mheap
中分配新的 span。
- 如果
- 返回分配的对象地址。
1.2.2 释放内存 (mcache.releaseAll
)
当 GC 发生时,mcache
会将所有未使用的 spans 返还给 mheap
。
func (c *mcache) releaseAll() {
for i := range c.alloc {
s := c.alloc[i]
if s != nil {
mheap_.freeSpan(s) // 释放到 mheap
c.alloc[i] = nil
}
}
}
2. mheap 模块
2.1 mheap 数据结构
mheap
是全局的堆管理器,负责分配和回收大块内存(span),以及为 mcache
提供支持。它的源码定义在 runtime/mheap.go
。
type mheap struct {
spans []*mspan // 全局管理的 spans
freelist [numSpanClasses]*mspan // 每个 size class 的空闲列表
arenas [maxArenas]*heapArena // 内存分配的区域
lock mutex // 全局锁
...
}
字段解释:
spans
:- 按页索引管理的所有 spans。
freelist
:- 每个
size class
的空闲 span 链表。
- 每个
arenas
:- 堆内存分配的底层区域,映射到操作系统的物理内存。
lock
:- 对全局堆操作加锁,避免并发问题。
2.2 mheap 的主要方法
2.2.1 分配 span (mheap.allocSpan
)
当 mcache
需要新的 span 时,会调用 mheap.allocSpan
:
func (h *mheap) allocSpan(sc spanClass) *mspan {
lock(&h.lock) // 加锁,防止并发冲突
s := h.freelist[sc]
if s != nil {
h.freelist[sc] = s.next // 从 freelist 获取 span
unlock(&h.lock)
return s
}
...
unlock(&h.lock)
return h.grow(sc) // freelist 没有时,从 arenas 扩展
}
工作流程:
- 从
freelist
中取出一个空闲的 span。 - 如果
freelist
为空,调用grow
方法,从arenas
分配新的 span。
2.2.2 回收 span (mheap.freeSpan
)
当 mcache
或垃圾回收器释放内存时,调用 mheap.freeSpan
:
func (h *mheap) freeSpan(s *mspan) {
lock(&h.lock) // 加锁
sc := s.spanclass()
s.reset() // 重置 span 状态
s.next = h.freelist[sc]
h.freelist[sc] = s // 回收到 freelist
unlock(&h.lock)
}
工作流程:
- 通过
spanclass
确定 span 类型。 - 重置 span 的元数据。
- 将 span 加入
freelist
链表。
2.3 mheap 内存增长 (mheap.grow
)
当 freelist
无法满足分配请求时,从底层 arenas
分配新的 span:
func (h *mheap) grow(sc spanClass) *mspan {
p := sysAlloc(_PageSize * npage, &memstats.heap_sys) // 从操作系统分配物理内存
if p == nil {
throw("out of memory")
}
s := newMSpan() // 创建新的 span
s.init(p, npage)
...
return s
}
3. mcache 与 mheap 的协作流程
- 分配内存:
- 小对象: 先从
mcache
中分配。 - 大对象: 直接通过
mheap
分配。
- 小对象: 先从
- 回收内存:
mcache
释放的内存会回收到mheap
,进入freelist
。
- GC 的作用:
- 清理未使用的对象。
- 调用
mcache.releaseAll
和mheap.freeSpan
释放无用的 span。
4. 小结一下
mcache
是每个 P (逻辑处理器) 的本地缓存,优化小对象分配的性能。mheap
是全局堆管理器,负责大对象分配和全局内存回收。- 两者通过
span
的共享和回收机制协作,兼顾性能与内存利用率。 - 垃圾回收器(GC)在这个体系中扮演清理者的角色,保证内存的高效使用。
关注威哥爱编程,成长路上一起努力,点个赞再走呗。
- 点赞
- 收藏
- 关注作者
评论(0)