go语言内存 go语言内存申请和释放( 二 )


接下来我们一起剖析一下Part2 ,看看它的内部到底和上一位之间有什么区别,才导致了这样的结果?
符合规则 2 , 不需要额外对齐
Part2 内存布局:ecax|bbbb|dddd|dddd
通过对比Part1和Part2的内存布局,你会发现两者有很大的不同 。如下:
仔细一看 ,  Part1存在许多 Padding 。显然它占据了不少空间,那么 Padding 是怎么出现的呢?
通过本文的介绍,可得知是由于不同类型导致需要进行字节对齐,以此保证内存的访问边界
那么也不难理解,为什么 调整结构体内成员变量的字段顺序 就能达到缩小结构体占用大小的疑问了,是因为巧妙地减少了 Padding 的存在 。让它们更 “紧凑” 了 。这一点对于加深 Go 的内存布局印象和大对象的优化非常有帮
(十一)golang 内存分析编写过C语言程序的肯定知道通过malloc()方法动态申请内存go语言内存,其中内存分配器使用的是glibc提供的ptmalloc2 。除了glibc,业界比较出名的内存分配器有Google的tcmalloc和Facebook的jemalloc 。二者在避免内存碎片和性能上均比glic有比较大的优势,在多线程环境中效果更明显 。
Golang中也实现了内存分配器,原理与tcmalloc类似,简单的说就是维护一块大的全局内存,每个线程(Golang中为P)维护一块小的私有内存,私有内存不足再从全局申请 。另外,内存分配与GC(垃圾回收)关系密切,所以了解GC前有必要了解内存分配的原理 。
为了方便自主管理内存,做法便是先向系统申请一块内存,然后将内存切割成小块 , 通过一定的内存分配算法管理内存 。以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示go语言内存:
预申请的内存划分为spans、bitmap、arena三部分 。其中arena即为所谓的堆区,应用中需要的内存从这里分配 。其中spans和bitmap是为了管理arena区而存在的 。
arena的大小为512G , 为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;
spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB)乘以指针大小8byte = 512M
bitmap区域大小也是通过arena计算出来,不过主要用于GC 。
span是用于管理arena页的关键数据结构 , 每个span中包含1个或多个连续页,为了满足小对象分配,span中的一页会划分更小的粒度 , 而对于大对象比如超过页大小,则通过多页实现 。
根据对象大小,划分了一系列class , 每个class都代表一个固定大小的对象 , 以及每个span的大小 。如下表所示:
上表中每列含义如下:
class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
bytes/obj:该class代表对象的字节数
bytes/span:每个span占用堆的字节数,也即页数乘以页大小
objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)waste
bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)上表可见最大的对象是32K大小,超过32K大小的由特殊的class表示,该class ID为0,每个class只包含一个对象 。
span是内存管理的基本单位,每个span用于管理特定的class对象, 跟据对象大?。瑂pan将一个或多个页拆分成多个块进行管理 。src/runtime/mheap.go:mspan定义了其数据结构:
以class 10为例,span和管理的内存如下图所示:
spanclass为10,参照class表可得出npages=1,nelems=56,elemsize为144 。其中startAddr是在span初始化时就指定了某个页的地址 。allocBits指向一个位图,每位代表一个块是否被分配,本例中有两个块已经被分配,其allocCount也为2 。next和prev用于将多个span链接起来,这有利于管理多个span,接下来会进行说明 。

推荐阅读