管理模型
-
池
程序动态申请内存空间, 是要使用系统调用的, 比如 Linux 系统上是调用
mmap方法实现的. 但对于大型系统服务来说, 直接调用mmap申请内存,会有一定的代价. 比如:- 系统调用会导致进程进入内核态, 内核分配完内存后(对虚拟地址和物理地址进行映射等操作), 再返回用户态.
- 频繁申请很小内存空间, 容易出现大量内存碎片, 增大OS整理碎片的压力.
- 为了保证内存访问具有良好的局部性, 开发者需要投入精力去做优化, 这是一个很重要的负担.
如何解决上面的问题呢? 有经验的人, 可能很快就想到了解决方案, 那就是我们常说的
对象池(也可以说是缓存)假设系统需要频繁动态申请内存来存放一个数据结构, 比如
[10]int. 那么我们完全可以在程序启动之初, 一次性申请几百甚至上千个[10]int. 这样就完美的解决了上面遇到的问题:- 不需要频繁申请内存了, 而是从对象池里拿, 程序不会频繁进入内核态
- 因为一次性申请一个连续的大空间, 对象池会被重复利用, 不会出现碎片
- 程序频繁访问的就是对象池背后的同一块内存空间, 局部性良好
这样会造成一定的内存浪费, 我们可以定时检测对象池的大小, 保证可用对象的数量在一个合理的范围, 少了就提前申请, 多了就自动释放.
如果某种资源的申请和回收是昂贵的, 我们都可以通过建立资源池的方式来解决, 比如连接池, 内存池等等, 都是一个思路.
-
Go的内存管理本质
就是一个内存池, 只不过内部做了很多的优化. 比如自动伸缩内存池大小, 合理的切割内存块等等.
-
概念
-
page: 内存页, 一块 8K 大小的内存空间. Go 与 OS之间的内存申请和释放都是以page为单位的 -
span: 内存块, 一个或多个连续的page组成一个span. 如果把page比喻成工人,span可以看成是小队, 工人被分成若干个队伍, 不同队伍干不同的(sizeclass)活 -
sizeclass: 空间规格, 每个span都带有一个sizeclass, 标记着该span中的page应该如何使用. 标志着span是一个什么样的队伍. -
object: 对象, 用来存储一个变量数据内存空间, 一个span在初始化时,会被切割成一堆等大的object. 假设object的大小是 16B,span大小是 8K, 那么就会把span中的page就会被初始化8K / 16B = 512个object. 所谓内存分配, 就是分配一个object出去. -
内存碎片
系统(OS/各种runtime)在内存管理过程中, 会不可避免的出现一块块无法被使用的内存空间, 这就是内存管理的产物.
-
内部碎片
一般都是因为字节对齐,如上面介绍 Tiny 对象分配的部分; 为了字节对齐, 会导致一部分空间直接被放弃掉, 不做分配使用.
-
外部碎片
一般时因为内存的不断分配和释放, 导致一些释放的小内存块分散在内存各处, 无法被用以分配. 不过Go的内存管理机制不会引起大量外部碎片.
不同颜色代表不同的
span不同
span的sizeclass不同, 表示里面的page将会按照不同的规格切割成一个个等大的object用作分配 span:用来存储 page 和 span 信息,比如一个 span 的起始地址是多少,有几个page, 已经使用了多大等等。 -
bitmap:存储着各个 span 中对象的标记信息,比如对象是否可回收等等。
arena_start:将要分配给应用程序使用的空间
-
内存池 mheap
Go 的程序在启动之初, 会一次性从OS那里申请一大块内存作为内存池. 这块内存空间会放在一个叫
mheap的struct中管理, mheap 负责将这一整块内存切割成不同的区域, 并将其中一部分的内存切割成合适的大小, 分配给用户使用. -
mcentral
用途相同(
sizecliass相同, 用来存储哪种大小的对象)的span会以链表的形式组织在一起. 比如当分配一块大小为 n 的内存时, 系统计算 n 应该使用哪种sizeclass, 然后根据sizeclass的值去找到一个可用的span来用作分配.找到合适的
span后, 会从中取一个object返回给上层使用. 这些span被放在一个叫做 mcentral 的结构中管理.mheap 将从 OS 那里申请过来的内存初始化成一个大
span(sizeclass=0). 然后根据需要从这个大span中切出小span, 放在mcentral中来管理.大
span由mheap.freelarge和mheap.busylarge等管理.如果 mcentral 中的
span不够用了, 会从mheap.freelarge上再切一块, 如果mheap.freelarge空间不够, 会再次从OS那里申请内存重复上述步骤. 这种方式可以避免出现外部碎片, 因为同一个 span 是按照固定大小分配和回收的, 不会出现不可利用的一小块内存把内存分割掉.