• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

图解Go语言内存分配

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理。这样可以自主地实现更好的内存使用模式,比如内存池、预分配等等。这样,不会每次内存分配都需要进行系统调用。

Golang运行时的内存分配算法主要源自 Google 为 C 语言开发的 TCMalloc算法,全称 Thread-CachingMalloc。核心思想就是把内存分为多级管理,从而降低锁的粒度。它将可用的堆内存采用二级分配的方式进行管理:每个线程都会自行维护一个独立的内存池,进行内存分配时优先从该内存池中分配,当内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争。

为了更好的阅读体验,手动贴上文章目录:

基础概念

Go在程序启动的时候,会先向操作系统申请一块内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理。

申请到的内存块被分配了三个区域,在X64上分别是512MB,16GB,512GB大小。

arena区域就是我们所谓的堆区,Go动态分配的内存都是在这个区域,它把内存分割成 8KB大小的页,一些页组合起来称为 mspan

bitmap区域标识 arena区域哪些地址保存了对象,并用 4bit标志位表示对象是否包含指针、 GC标记信息。 bitmap中一个 byte大小的内存对应 arena区域中4个指针大小(指针大小为 8B )的内存,所以 bitmap区域的大小是 512GB/(4*8B)=16GB。如下图:

从上图其实还可以看到bitmap的高地址部分指向arena区域的低地址部分,也就是说bitmap的地址是由高地址向低地址增长的。

spans区域存放 mspan(也就是一些 arena分割的页组合起来的内存管理基本单元,后文会再讲)的指针,每个指针对应一页,所以 spans区域的大小就是 512GB/8KB*8B=512MB。除以8KB是计算 arena区域的页数,而最后乘以8是计算 spans区域所有指针的大小。创建 mspan的时候,按页填充对应的 spans区域,在回收 object时,根据地址很容易就能找到它所属的 mspan

内存管理单元

mspan:Go中内存管理的基本单元,是由一片连续的 8KB的页组成的大块内存。注意,这里的页和操作系统本身的页并不是一回事,它一般是操作系统页大小的几倍。一句话概括: mspan是一个包含起始地址、 mspan规格、页的数量等内容的双端链表。

每个 mspan按照它自身的属性 SizeClass的大小分割成若干个 object,每个 object可存储一个对象。并且会使用一个位图来标记其尚未使用的 object。属性 SizeClass决定 object大小,而 mspan只会分配给和 object尺寸大小接近的对象,当然,对象的大小要小于 object大小。还有一个概念: SpanClass,它和 SizeClass的含义差不多,



  1. Size_Class = Span_Class / 2


这是因为其实每个 SizeClass有两个 mspan,也就是有两个 SpanClass。其中一个分配给含有指针的对象,另一个分配给不含有指针的对象。这会给垃圾回收机制带来利好,之后的文章再谈。

如下图, mspan由一组连续的页组成,按照一定大小划分成 object

Go1.9.2里 mspanSizeClass共有67种,每种 mspan分割的object大小是8*2n的倍数,这个是写死在代码里的:



  1. // path: /usr/local/go/src/runtime/sizeclasses.go



  2. const _NumSizeClasses = 67



  3. var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536,1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}


根据 mspanSizeClass可以得到它划分的 object大小。 比如 SizeClass等于3, object大小就是32B。 32B大小的object可以存储对象大小范围在17B~32B的对象。而对于微小对象(小于16B),分配器会将其进行合并,将几个对象分配到同一个 object中。

数组里最大的数是32768,也就是32KB,超过此大小就是大对象了,它会被特别对待,这个稍后会再介绍。顺便提一句,类型 SizeClass为0表示大对象,它实际上直接由堆内存分配,而小对象都要通过 mspan来分配。

对于mspan来说,它的 SizeClass会决定它所能分到的页数,这也是写死在代码里的:



  1. // path: /usr/local/go/src/runtime/sizeclasses.go



  2. const _NumSizeClasses = 67



  3. var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}


比如当我们要申请一个 object大小为 32Bmspan的时候,在classtosize里对应的索引是3,而索引3在 class_to_allocnpages数组里对应的页数就是1。

mspan结构体定义:



  1. // path: /usr/local/go/src/runtime/mheap.go



  2. type mspan struct {



  3.    //链表后向指针,用于将span链接起来



  4.    next *mspan



  5.    //链表前向指针,用于将span链接起来



  6.    prev *mspan



  7.    // 起始地址,也即所管理页的地址



  8.    startAddr uintptr



  9.    // 管理的页数



  10.    npages uintptr



  11.    // 块个数,表示有多少个块可供分配



  12.    nelems uintptr



  13.    //分配位图,每一位代表一个块是否已分配



  14.    allocBits *gcBits



  15.    // 已分配块的个数



  16.    allocCount uint16



  17.    // class表中的class ID,和Size Classs相关



  18.    spanclass spanClass  



  19.    // class表中的对象大小,也即块大小



  20.    elemsize uintptr



  21. }


我们将 mspan放到更大的视角来看:

上图可以看到有两个 S


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
linux查看端口号占用命令-netstat - go2coding发布时间:2022-07-10
下一篇:
go语言中的timer和ticker定时任务发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap