一般程序的内存分配,从高位到低位依次为
全局静态区:用于存储全局变量、静态变量等;这部分内存在程序编译时已经分配好,由操作系统管理,速度快,不易出错。
栈:函数中的基础类型的局部变量;由程序进行系统调用向操作系统申请,由操作系统管理,速度快。每个线程有自己的栈区。
堆:使用malloc或new申请的内存;由程序运行过程中动态分配任意大小的内存,由程序管理,使用free或者delete删除;频繁的分配和释放必然导致内存碎片。
常量区:存放常量字符串,程序结束后由系统释放。
程序代码区:存放程序的二进制代码。
Go的内存分配:
Go是内置运行时runtime的语言;像这种内置运行时的语言会抛弃传统的内存管理方式,改为自己管理;这样可以完成类似预分配,内存池等操作,以避开系统调用产生的性能问题。Go的内存分配可以分为以下几点:
1. 每次从操作系统申请一大块内存,由Go来对这块内存做分配,减少系统调用。
2. 内存分配算法采用 TCMalloc算法,核心思想是把内存分的非常细,进行分级管理,以降低锁的粒度。
3. 回收对象内存时,并没有将其真正释放掉,而是放回预先分配的大内存中,以便复用。只有内存闲置过多的时候,才会尝试归还部分内存给操作系统,降低整体开销。
Go在程序启动的时候,会分配一块连续的内存(虚拟内存),整体如下:
arena就是所谓的堆区,他把内存分割成 8K大小的页,页是heap中的最小管理单位,一些页组合起来形成了mspan。
bitmap区用于保存arena对应地址(指针大小为 8B,bitmap中一个byte大小的内存对应arena区域中的4个指针,因此大小为 512G/(4 * 8B))中是否保存了对象,以及对象是否被gc过,主要用于gc。
spans区域存放了mspan的指针,用于表示arena区的某一页属于哪个mspan。大小为 512G / 8KB(页的大小) * 8B(指针大小)。在创建 mspan的时候,按页填充对应的spans区域,在回收object时,很容易找到他所属的mspan。
Go内存管理结构
1. class和mspan
go对象管理粒度分为67种大小,也叫class和块。一个page根据class大小分为8K/class size。
class相同且地址连续的pages组成一个span。mspan是span的实际结构,mspan之间组成双向链表关联。
2. mcache
每个工作线程都有各自的mcache,本地缓存可用的mspan资源,这样各个线程在申请内存的时候无需加锁,对于小于32KB大小的对象,直接通过mcache分配;对于大于23KB的大对象,则需要在mheap中分配。
每个goroutine对于同一个class都有scan和noscan两种mspan队列,其中scan分配给含有指针的对象,noscan分配给不含指针的对象,这样做是为了便于进行垃圾回收,在进行垃圾回收的时候,对于不包含指针的对象,无需进一步扫描是否引用其他活跃对象。
3. mcenter
mcenter为所有mcache提供切分好的mspan。有多少种mspan类型的mspan就有多少个mcenter,每个mcenter保存一种特定类型的mspan,提供两个mspan双端队列,包括已分配出去的和未分配出去的。
所有线程共享,需要加锁访问,但是申请一种 类型的mspan不会影响其他的mcenter.
type mcentral struct {
lock mutex
spanclass spanClass
nonempty mSpanList // list of spans with a free object, ie a nonempty free list
empty mSpanList // list of spans with no free objects (or cached in an mcache)
// nmalloc is the cumulative count of objects allocated from
// this mcentral, assuming all spans in mcaches are
// fully-allocated. Written atomically, read under STW.
nmalloc uint64
}
4. mheap
代表Go程序的堆空间,Go程序使用一个mheap来管理堆空间。
当mcenter中没有空闲的mspan时,会向mheap申请。而mheap没有剩余内存时,会向操作系统申请新内存。
另外,对于大于32Kb的大对象,需要在mheap中分配,所有线程共享,需要加锁。
参考:
https://zhuanlan.zhihu.com/p/59125443
手机扫一扫
移动阅读更方便
你可能感兴趣的文章