go语言的内存管理着重解决了如下问题
- 进程内内存管理:通过多层内存池尽量减少与操作系统内核的系统调用。
- 内存碎片管理:尽量减少内存碎片,提高内存利用率。
1 一般程序内存的基本分区
说到带内存回收运行时的内存管理,主要指的是堆区。本文也主要描述堆区。下图表示程序的一般内存组成。
2. go堆内存管理
go的内存管理思想基于TCMalloc (线程缓存分配Thread-Caching Malloc)
。
即
- 应用内存池的概念
- 把内存分为多种不同大小的块
- 根据程序生成对象大小选择最合适的内存块分配内存
- 从而减少内存碎片和系统内存申请次数
2.1 go早期的堆内存管理方式
go早期使用线性的内存管理方式,存在内存大小受限以及和C混编存在内存冲突的问题
对于内存池分块的管理方式,如下图
go1.10之前是先划定固定的虚拟内存
,spans区结构体指针指向arena区。内存都是固定的连续块
。可以看出,最大堆arena被限制在了512G
。同时因为堆是连续的。当和C语言混合编程时,存在内存冲突
导致程序崩溃的问题。
2.2 go现在的堆内存管理方式
主要体现在用二维的runtime.heapArea数组管理离散型的堆区内存。
如下图,下图同时也包含了内存池及分块的细节描述
从图中可以看出:
- 一个处理器对应一个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