go语言的内存自动分配和回收的,因此内存的使用流程大致为:获取内存——分配内存——回收内存——再分配内存。
其中分配内存分为两方面,堆内存分配和栈内存分配,堆内存和栈内存是两种不同的分配方式,本篇文章主要是堆内存的分配方式。
1. 自主内存管理
Go语言为了方便内存的自主管理,一开始启动的时候会向操作系统申请一大块连续的内存,后续的内存分配都是基于这一大块内存的。如果程序运行过程中,内存不足了,Go语言会再向操作系统进行内存的申请。
为了高效的进行内存管理,Go语言将申请到的内存划分为三个区域,分别为spans、bitmap、arena。其中arena是堆区,GO语言将其划分为一个个8KB的page页,程序运行过程中的内存都是从arena堆区进行分配的。spans和bitmap是为了对arena堆区的高效管理而设置的。
2. mspan 和 ClassID表
我们程序的运行,从使用的角度看,以对象为基本单位的。比如:函数是一个对象、goroutine是一个对象、slice是一个对象。因此,为了实现从对象到内存页之间的映射,Go语言引入了一个mspan结构体。mspan是内存管理的基本单位,每一个span包含一个或多个连续的页。为了满足小对象的分配,会将span中的一页划分为更小的粒度,而对于大对象比如超过页的大小,则会通过多页实现。每个span用于管理特定的class对象,根据对象的大小,span将一个或多个页拆分成多个块进行管理。
mspan的结构体定义如下:位置 runtime:mheap.go 一些字段已经省略了
type mspan struct {
next *mspan // 链表后向指针,用于将span链接起来
prev *mspan // 链表前向指针,用于将span链接起来
startAddr uintptr // 起始地址,即所管理页的地址
npages uintptr // 该span中占用的page页数
//作用于GC,垃圾会有的时候会使用这三个字段
allocBits *gcBits //分配位图,每一位代表一个块是否已分配
gcmarkBits *gcBits
pinnerBits *gcBits
allocCount uint16 // 该Span中已经分配的对象数量
spanclass spanClass // 该Span的等级,一个特定的span中存储特定的ClassID大小的对象
elemsize uintptr // 块大小,该span中存储特定ClassID的大小
}
Class等级列表如下:
源码位置:runtime:sizeclasses.go
划分如此多的Class等级的目的:
- 尽可能地减少内存浪费
- 降低从全局缓存中获取特定SpanClass锁的粒度
// class bytes/obj bytes/span objects tail waste max waste min align
// 1 8 8192 1024 0 87

最低0.47元/天 解锁文章
2080

被折叠的 条评论
为什么被折叠?



