基本策略:
1.每次从操作系统申请一大块内存,减少系统调用
2.将申请到的大块内存按照特定大小预先切分成小块,构成链表
3.为对象分配内存时,只需从大小合适的链表中提取一个小块即可。
4.回收对象内存时,将该小块内存重新归还到原链表,以便复用。
5.如闲置内存过多,则尝试归还部分内存给操作系统,降低整体开销。
内存块
-
span
有多个地址连续的页组成的大块内存(面向内部管理)
分配器按页数区分不同大小的span,以页数为索引在管理数组中查找。没有大小合适的span时就裁剪页数更多的span,将多余部分构成新的span放回管理数组。
分配器也会合并地址相邻的空闲span,获取更大内存块,减少碎片。 -
object
将span按特定大小切分成多个小块,每个小块可存储一个对象(面向对象分配)
按8字节倍数分为n种,有限几种规格的小块内存。
分配器初始化时,会构建对照表:存储大小-规格,包括用来切分的span页数,若对象大小超过特定阈值限制,会被当做大对象特别对待
管理组件
采用tmalloc的架构
分配器由三种组件组成:
-
cache
每个运行期工作线程都会绑定一个cache,用于无锁object分配
是实现高性能无锁分配的核心,工作线程私有而不被共享 -
central
为所有cache提供切分好的后备span资源
在多个cache间提高object的利用率,避免内存浪费 -
heap
管理闲置span,需要时向操作系统申请新内存
在不同object需求间平衡
分配流程:
释放流程:
初始化
初始化阶段保留一段很大的虚拟地址空间,分为三个区域
spans 512MB | bitmap 32GB | arena 512GB
其属性都保存在heap中
1.使用arena地址向操作系统申请内存,大小决定可分配用户内存的上限
2.位图bitmap为每个对象提供4bit标记位,保存指针,GC标记等信息
3.创建span时,按页填充对应spans空间。回收object时,将地址按页对齐后就有所属span
heap初始化
分配
分配内存需要区分分配在堆上还是栈上(尽可能使用寄存器和栈来存储对象)
new函数不一定分配在堆上
没有内联时,需在两个栈帧间传递对象,所以会在堆上分配而非返回一个失效栈帧里的数据
内联后,实际上成为main栈帧内的局部变量,无需堆上操作。
malloc分配:
- 大对象:从heap获取span
- 小对象:cache.alloc[sizeclass].freelist获取object
- 微小对象:组合使用cache.tiny object
微小对象不是指针,它从span.freelist获取一个16字节的object,然后利用偏移量来记录下一次分配的位置。
heap获取span:找到大小最合适的块
回收
内存回收的源头时垃圾清理操作
内存分配器的核心是内存复用——不再使用的内存会被放回合适的位置,等下次分配时再使用。
回收操作以span为基本单位,通过比对bitmap里的扫描标记,逐步将object收归原span,最终上交central或heap复用。
释放
监控任务sysmon每隔一段时间遍历free、freelarge里所有span,若闲置时间超过阈值,就释放其关联的物理内存。