Go语言抛弃C/C++中的开发者管理内存的方式,实现了主动申请与主动释放管理,增加了逃逸分析和垃圾回收,将开发者从内存管理中释放出来。
存储金字塔
冯·诺依曼计算机体系中的存储器,是用于存储程序和数据的。现代计算机系统中,一般都是采用“CPU寄存器-CPU高速缓存-内存-硬盘”的存储器结构。自上而下容量逐渐增大,速度逐渐减慢,单位价格逐渐降低。
1、CPU寄存器:存储CPU正在使用的数据或指令。
2、CPU高速缓存:存储CPU近期要用到的数据和指令。
3、内存:存储正在运行或者将要运行的程序和数据。
4、硬盘:存储暂时不使用或者不能直接使用的程序和数据
物理内存:是指实际通过物理内存而获得的内存空间。
虚拟内存:与物理内存相反,是指根据系统需要从硬盘中虚拟的划出一部分存储空间。
虚拟内存技术就是对内存的一种抽象,有了这层抽象之后,程序运行进程的总大小可以超过实际可用的物理内存大小。每个进程都有自己的独立虚拟地址空间,然后通过CPU和MMU把虚拟内存地址转换为实际物理地址。
TCMalloc
TCMalloc全称是Thread Cache Malloc,是google为C语言开发的内存分配算法,是Go内存分配的起源。
TCMalloc内存分配算法的核心思想是把内存分为多级管理,从而降低锁的粒度,它将可用的堆内存采用二级分配的方式进行管理,每个线程都会自行维护一个独立的线程内存池,进行内存分配时优先从该线程内存池中分配, 当线程内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争 ,进一步的降低了内存并发访问的粒度。
Go的内存分配算法是基于TCMalloc内存分配算法实现的,借鉴了TCmalloc的思想。
Page: 操作系统对内存的管理同样是以页为单位,但TCMalloc中的Page和操作系统的中页是倍数关系,x64下Page大小为8KB。
Span:一组连续的Page被叫做Span,是TCMalloc内存管理的基本单位,有不同大小的Span,比如2个Page大的Span,16个Page大的Span。
ThreadCache:每个线程各自的Cache,每个ThreadCache包含多个不同规格的Span链表,叫做SpanList,内存分配的时候,可以根据要分配的内存大小,快速选择不同大小的SpanList,在SpanList上选择合适的Span,每个线程都有自己的ThreadCache,所以ThreadCache是无锁访问的。
CentralCache:中心Cache,所有线程共享的Cache,也是保存的SpanList,数量和ThreadCache中数量相同,当ThreadCache中内存不足时,可以从CentralCache中获取,当ThreadCache中内存太多时,可以放回CentralCache,由于CentralCache是线程共享的,所以它的访问需要加锁。
PageHeap:堆内存的抽象,同样当CentealCache中内存太多或太少时,都可从PageHeap中放回或获取,同样,PageHeap的访问也是需要加锁的。
综合3种语言对比,可以看到既有共同交集的地方,也有各自的私有属性特色,各自的管理分配方式用到自己的语言环境下都能发挥最大的作用和效率。这也验证了一句至高的哲学:方案设计或者架构理念,是没有最优秀最完美的,但是会有最适合最贴近使用场景的。