go的堆内存结构分析

前面几章内容
(一) go协程栈底层讲解
(二) go的堆内存结构分析
(三) 高级语言垃圾回收思路和如何减少性能影响原理分析

3. go的堆内存结构是怎样的

在go 中堆和栈都是用的堆内存
研究go 的堆内存的概念先弄清楚操作系统的虚拟内存

3.1 操作系统虚拟内存

不是win 的虚拟内存(相当于linux 中的swap)指的是内存不够时候拿出来一块硬盘做虚拟内存

操作系统给应用提供的虚拟内存空间(在老的linux32位机器上,操作系统给每个进程一个4GB 的虚拟空间),因为操作系统不允许进程动物理内存的,因为物理内存是多个进程共用的(动了别人的内存,那么其他进程可能就崩溃了),所以说操作系统给每个进程提供的是虚拟内存,然后操作系统管理虚拟内存到物理内存之间的关系

虚拟内存背后是物理内存,也有可能有磁盘

Linux 获取虚拟内存: mmap、madvice

3.2 操作系统虚拟内存(linux)和进程之间的关系

下面是以一台64位物理机,64GB内存,展示了 进程和物理内存之间隔着一个虚拟内存
go的堆内存结构分析_第1张图片

3.3 go中虚拟内存是怎么获取的?

有上图可以知道每个进程有独立的虚拟内存,那么现在在go程序中虚拟内存是怎么获取的?
是通过一个一个变量获取还是一批批获取的,答案是一批批获取的,在go中有个这样一个结构体 heapArena

3.4 heapArena 结构体

  1. Go 每次申请的虚拟内存单元为64MB
  2. 最多有4194304 个虚拟内存单元(2^20)
  3. 内存单元也叫heapArena
  4. 所有的heapArena 组成了mheap (Go 堆内存)

mheap 与 heapArena 关系示意图:
go的堆内存结构分析_第2张图片
注意下面的换算和细节:

2^20 * 64MB = 256 TB
heapArena 中相邻的内存块对应的虚拟内存不一定相邻
虚拟内存中相邻的对应的物理内存也不一定相邻

在go中虚拟内存是以heapArena 结构体方式 对应,那么每一块这样的内存在go中是如何使用的呢,这里有三种方式

  • 线性分配
  • 链表分配
  • 分级分配

3.5 线性分配heapArena

  1. 放入一个对象
  2. 当放入这么多对象时(外部也叫值)
  3. 如果现在其中两个对象没用,被GC后,回收两个对象后
  4. 后面进来的新对象怎么分配,直接在后面进行分配

上面的做法才是线性分配
go的堆内存结构分析_第3张图片

3.5 链表分配heapArena

  1. 直接把空闲的位置用链表统计起来
  2. 如果遇到很大的对象,之前还是不能使用,这就会造成碎片化内存
    go的堆内存结构分析_第4张图片

3.5 分级分配heapArena

线性分配或者链表分配容易出现空间碎片, 如图
go的堆内存结构分析_第5张图片
分级分配的思想是,有如下几步

  1. 先把大的内存拿过来后分成很多小块
  2. 将对象放入能放进去的最小箱子(2,3图)
  3. 回收对象后,下一次有对象来了,就直接放入空闲的空间里面

go的堆内存结构分析_第6张图片

go的堆内存结构分析_第7张图片

3.6 分级的思想中的级(内存管理单元 mspan)

  1. 根据隔离适应策略,使用内存时的最小单位为mspan
  2. 每个mspan 为N 个相同大小的格子
  3. Go 中一共有67种mspan

go的堆内存结构分析_第8张图片
最后一栏是指最大浪费
比如说8字节的格子,最小放入1字节对象,就浪费了87%的空间
这个表中sizeclass.go 文件中可看到

go的堆内存结构分析_第9张图片
这里是根据对象的需要去开辟的,不是所有都有全部的67级

3.7 内存管理单元与中心索引(mcentral)

每个heapArena 中的mspan 都不确定,那么我们该如何快速找到所需的mspan 级别呢?

这里如何快速找到就是内存管理单元需要做的事了,这里就用到了中心索引的方式

3.7.1 中心索引

在每个协程中,有136个mcentral 结构体,其中68个组需要GC 扫描的mspan,68个组不需要GC 扫描的mspan

关系示意图:
go的堆内存结构分析_第10张图片
在mcentral.go 文件中,有结构体

需要扫描和不需要扫描上面虚线部分,不是真正的内存,只是一个索引,它把下面的heapArena 开辟的内存一组一组索引起来,简单来说它就是下面heapArena的目录

3.8 mcentral 的性能问题

  1. mcentral 实际是中心索引,使用互斥索引
  2. 在高并发场景下,锁冲突问题严重
  3. 参考协程GMP模型,增加线程本地缓存

3.9 线程缓存mcache

  1. 每个P拥有一个mcache
  2. 一个mcache 拥有136个mspan,其中
  3. 68 个需要GC扫描的mspan
  4. 68个不需要GC扫描的mspan

线程缓存和架构之间的关系
go的堆内存结构分析_第11张图片
工作原理就是:有一个中央的索引class0-67 虚线部分
但是有性能问题于是在线程中开辟了一个本地缓存索引

3.10 小结

  1. Go模仿TCmalloc(C++的内存的结构),建立了自己的堆内存架构
  2. 使用heapArena 向操作系统申请内存
  3. 使用heapArena时,以mspan 为单位,防止碎片化
  4. mcentral 是mspan 们的中心索引
  5. mcache 记录了分配给各个P 的本地mspan

4. go是如何分配堆内存的

回顾上一节内容,看下图
go的堆内存结构分析_第12张图片
他们之间的关系理解:
因为heapArena 中分了很多的小块,才有了中央索引;中央索引因为性能问题才有了本地缓存索引

4.1 对象分级

go分配堆内存前,会按照对象的大小进行不同的分配,那么对象的大小是如何定义的呢,下面是针对对象大小的一个定义:

  1. Tiny 微对象(0,16B)无指针

  2. Small 小对象[16B, 32KB]

  3. Large 大对象(32KB, +无穷大)

  4. 微小对象(32KB以下的)分配 到普通mspan (1-67级span)

  5. 大对象 量身定做mspan(0级span)(0级span 是没有固定大小的)

4.2 微对象分配

  1. 从mcache 拿到2级mspan
  2. 将多个微对象合并一个16Byte 存入
    在这里插入图片描述

4.3 mcache 的替换

  1. mcahche中,每个级别的mspan 只有一个
  2. 当mpan 满了之后,会从mcentral 中换一个新的

4.4 mcentral 的扩容

  1. mcentral中,只有有限数量的mspan
  2. 当mspan缺少时,会从heapArena 开辟新的mspan (在heapArena一栏中申请,进而向操作系统中申请)

4.5 大对象分配

之前我们探讨的只有1-67级,但实际上总共有68级,那么0级主要作用是什么,其实0级有很大的作用,看下面:

  1. 直接从heapArena 开辟0级的mspan
  2. 0级的mspan 为大对象定制

mallocgc.go
go的堆内存结构分析_第13张图片
go的堆内存结构分析_第14张图片

4.6 heapArena 的扩充

  1. 当heapArena空间不足时
  2. 向操作系统申请新的heapArena(基本大小是64MB)

4.7 小结

  1. Go将对象按照大小分为3种
  2. 微小对象使用mcache
  3. mcache中的mspan 填满后,与mcentral 交换新的
  4. mcentral 不足时,在heapArena 开辟新的mspan
  5. 大对象直接在heapArena 开辟新的mspan

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

你可能感兴趣的:(【go专栏】从原理解析go语言,golang,linux,java)