内存分配器
线性分配器
线性分配器有较快的执行速度、较低的实现复杂度,但是无法在内存被释放时重用内存。
线性分配器需要配合使用具有拷贝特性的垃圾回收算法,如标记压缩(Mark-compact)、复制回收(Copying GC)和分代回收(Generational GC)等;通过拷贝的方式整理存活对象的碎片,将空闲内存定期合并。
空闲链表分配器
以链表的形式连接不同的内存块,所以可以重用已经释放的内存;
-
分配内存时需要遍历链表,从链表中选择合适的内存块,有四种分配策略:
首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块;
循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块;
最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
隔离适应(Segregated-Fit)— 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块,这种分配策略减少了需要遍历的内存块数量,提高了内存分配的效率,Go语言中使用的内存分配策略,与这种类似;
分级分配
-
Go语言的内存分配器是借鉴了TCMalloc的设计,将对象根据大小分类并使用多级缓存,实施不同的分配策略;
类别:微对象 (0, 16B)、小对象[16B, 32KB]、大对象(32KB, +∞)
多级缓存:线程缓存(Thread Cache)、中心缓存(Central Cache)和页堆(Page Heap)
-
线程缓存属于独立的线程,不需要互斥锁、减少了锁竞争上的性能损耗,能满足绝大多数的内存分配需求,当不能满足时会使用中心缓存解决小对象的内存分配问题,遇到32KB以上的对象时内存分配器会选择页堆直接分配内存;
2fad4a70d28768eac488b9b00d56639b.png
虚拟内存布局
- Go语言1.10及以前版本使用线性的堆内存,这种设计简单且方便,但是在C和Go混合使用时会造成程序崩溃;
