go语言的内存管理着重解决了如下问题

  • 进程内内存管理:通过多层内存池尽量减少与操作系统内核的系统调用。
  • 内存碎片管理:尽量减少内存碎片,提高内存利用率。

1 一般程序内存的基本分区

说到带内存回收运行时的内存管理,主要指的是堆区。本文也主要描述堆区。下图表示程序的一般内存组成。

#621px #426px

2. go堆内存管理

go的内存管理思想基于TCMalloc (线程缓存分配Thread-Caching Malloc)

  • 应用内存池的概念
  • 把内存分为多种不同大小的块
  • 根据程序生成对象大小选择最合适的内存块分配内存
  • 从而减少内存碎片和系统内存申请次数

2.1 go早期的堆内存管理方式

go早期使用线性的内存管理方式,存在内存大小受限以及和C混编存在内存冲突的问题
对于内存池分块的管理方式,如下图

#632px #152px

go1.10之前是先划定固定的虚拟内存,spans区结构体指针指向arena区。内存都是固定的连续块。可以看出,最大堆arena被限制在了512G。同时因为堆是连续的。当和C语言混合编程时,存在内存冲突导致程序崩溃的问题。

2.2 go现在的堆内存管理方式

主要体现在用二维的runtime.heapArea数组管理离散型的堆区内存。

如下图,下图同时也包含了内存池及分块的细节描述

#721px #390px

从图中可以看出:

  • 一个处理器对应一个mcache,实现无锁申请(用户空间,无系统调用
  • mcache不足向mcentral申请(用户空间,无系统调用
  • mcentral不足则向mheap申请(系统空间,有系统调用)
  • heaparena双向链节点结构体指向离散的内存地址,解决连续内存空间的问题
  • 内存池种的基本单元是链表结构的mspan
  • 内存池的最小单元是page(8k大小),不同个数的page组成不同规格的mspan
  • 程序生成的对象根据对象大小分配不同的mspan从而减少内存碎片化

3. 附录

go 对象内存大小的分类如下

类别 大小
微对象 (0, 16B)
小对象 [16B, 32KB]
大对象 (32KB, +∞)

go语言对应的几个内存结构体

  • runtime.mspan
  • runtime.mcache
  • runtime.mcentral
  • runtime.mheap

不同平台堆heaparena的特点

平台 地址位数 Arena 大小 一维大小 二维大小
*/64-bit 48 64MB 1 4M (32MB)
windows/64-bit 48 4MB 64 1M (8MB)
*/32-bit 32 4MB 1 1024 (4KB)
*/mips(le) 31 4MB 1 512 (2KB)

解释:

runtime.mheap中heapArena是通过一个二维数组管理的
该数组

  • 在Linux amd64平台下为 [1][4194304]heapArena 简写为 [1][4M]heapArena
  • 在下windows amd64平台下为 [64][1048576]heapArena 简写为 [1][1M]heapArena

通过数组一维二维的大小和单个元数据及单个heapArea的大小就可以计算出总元数据和整个堆区的大小。

  • 如下在windows平台下,单个层面二维总元数据大小为 8B*1048576 = 8MB, 整个堆区大小为 4M*64*1048576 = 256TB
  • 如下在linux平台下,单个层面二维总元数据大小为 8B*4194304 = 32MB, 整个堆区大小为 64M*1*1048576 = 256TB