Go内存管理–内存分配一

前言

go作为一个比较新晚(新)的语言,自然借鉴前辈们的优点,比如说语言本身负责内存管理、对协程和高并发的高优支持、简单高效的语法等。本篇及后续的几篇要讲的就是还没提到的比较复杂的内存管理。 学习内存管理(分配&回收)前,如果有JVM的内存管理的基础,会变得非常简单,如果是第一次接触内存管理,在看完Go的内存管理后可以去看看JVM的,对比着学习比较容易理解。 go的内存管理思路是基于google 的tcmalloc(thread-caching-malloc)实现的,常见的内存分配器还有ptmalloc、jemalloc,但是tcmalloc的性能更高,尤其是高并发场景下。

关于tcmalloc

tcmalloc的分配的内存主要来源于:全局缓存堆进程私有缓存,小容量的内存申请使用私有缓存,如果私有缓存不够,则从全局缓存堆中申请一部分作为私有缓存。大对象会直接从全局缓存中申请,至于大小的分界是32k。

小对象分配

小对象内存分配默认会分配86个不同大小的块,这些块的大小不一致,内部采用单链表数组来组织内存,使用时遵循懒加载策略,等到使用时才进行初始化。

Go内存管理--内存分配一

大对象分配

大于32k的内存申请,属于大对象的分配,使用全局缓存堆直接分配,内存的组织方式也是通过单链表数组进行的,数据长度是256,每个链表的元素的大小是不同的,但都是4k(1page)的整数倍。

Go内存管理--内存分配一

Span

tcmalloc使用span来管理内存分页,一个span可以包含几个连续分页。span的状态只有未分配、作为大对象分配、作为小对象分配。

go内存分配

主流程

1、申请一块较大的虚拟内存空间,用于内存分配及管理 当空间不足时,向系统申请一块较大的内存,如100KB或者1MB 申请到的内存块按特定的size,被分割成多种小块内存(go:_NumSizeClasses = 67),并用链表管理。创建对象时,按照对象大小,从空闲链表中查找到最适合的内存块。 2、销毁对象时,将对应的内存块返还空闲链表中以复用。 3、空闲内存达到最大值时,返还操作系统。

管理组件

go将内存分为三个层级,协程私有内存,全局内存,整体内存叶管理,也因此有这三个层级的内存管理工具。

Go内存管理--内存分配一

mspan: mspan并不直接拥有内存空间,它负责管理起始地址为startAddr、级别(预分配页的个数)为sizeclass的连续地址空间。 mcache: Per-P私有cache,用于实现无锁的object分配,每个mcache有大小为67的mspan数组,存储不同级别大小的mspan。 mcentral: 全局内存,为各个cache提供按大小划分好的mspan,mcentral有个关键方法cacheSpan(),它是整个分配的核心算法 mheap 是真实拥有虚拟地址的结构,page管理,内存不足时向系统申请。 总体来说,他们之间的关系是这样的: Golang程序启动时申请一大块内存,并划分成spans、bitmap、arena区域,arena区域按页划分成一个个小块,span管理一个或多个页,mcentral管理多个span供线程申请使用,mcache作为线程私有资源,资源来源于mcentral。 本篇暂时就介绍这么多。