golang学习笔记(内存模型和分配机制)

操作系统的存储管理

虚拟内存管理

虚拟内存是一种内存管理技术,它允许操作系统为每个进程提供一个比实际物理内存更大的地址空间。这个地址空间被称为虚拟地址空间,而实际的物理内存则被称为物理地址空间。使用虚拟内存有以下几点好处:

内存抽象:虚拟内存为每个进程提供了一个连续的地址空间,使得每个程序都认为自己独占了整个内存,这样可以简化程序的编写和调试,开发人员不需要关心物理内存的具体布局。

内存保护:通过虚拟内存,每个进程都有自己的地址空间,互不干扰。这样可以防止一个程序错误地访问或修改另一个程序的内存,提高了系统的稳定性和安全性。

内存共享:虚拟内存允许多个进程共享相同的物理内存页,例如,多个进程可以共享相同的库或程序代码,这样可以节省内存空间。

支持更多的应用程序:虚拟内存通过使用硬盘空间作为额外的内存,在物理内存不足时,可以临时将不常用的内存页移出到硬盘上,从而释放内存空间。这样,系统就可以运行更多的应用程序,即使物理内存有限。

更有效的内存使用:操作系统可以通过虚拟内存管理机制,如页面置换算法,来优化内存的使用效率,确保最常用的数据和程序保留在物理内存中,而较少使用的部分则可以暂时存储在硬盘上。
在这里插入图片描述

进程的虚拟内存管理

每个进程的虚拟内存空间都会映射到物理内存,如何映射对于程序员来说是透明的不需要关心,虚拟内存到物理内存的映射由操作系统实现。内核态虚拟内存空间是所有进程共享的,不同进程进入内核态之后看到的虚拟内存空间全部是一样的。比如下图中进程 1、进程 2 在内核态都去访问虚拟地址,由于内核态这部分地址空间映射到的都是同一份物理内存,进程 1、进程 2 看到的内容也是一样的。
在这里插入图片描述
进程的用户空间被分为多个段:

  • 代码段:在进程运行之前,存放在二进制文件中的机器码需要被加载进内存中,用于存放这些机器码的虚拟内存空间叫做代码段。

  • 数据段:指定初始值的全局变量和静态变量,在程序运行之前这些全局变量和静态变量会被加载进内存中供程序访问,这段区域叫做数据段。

  • BBS段:没有指定初始值的全局变量和静态变量在虚拟内存空间中的存储区域叫做 BSS 段,这些未初始化的全局变量被加载进内存之后会被初始化为 0 值。BSS 段与数据段的区别是不会一开始就加入到内存中而是需要的时候从硬盘上加载。

  • :程序在运行期间往往需要动态的申请内存,所以在虚拟内存空间中也需要一块区域来存放这些动态申请的内存,这块区域就叫做堆。

  • :在程序运行的时候会调用函数,调用函数过程中使用到的局部变量和函数参数也需要一块内存区域来保存。这一块区域在虚拟内存空间中叫做栈。

  • 文件映射与匿名映射区:每当我们调用一次 mmap 进行内存映射的时候,内核都会在文件映射与匿名映射区中划分出一段虚拟映射区出来,这段虚拟映射区就是我们申请到的虚拟内存。

分页管理

操作系统中通常会将虚拟内存和物理内存切割成固定的尺寸,于虚拟内存而言叫作“页”,于物理内存而言叫作“帧”,原因及要点如下:

  • 提高内存空间利用(以页为粒度后,消灭了不稳定的外部碎片,取而代之的是相对可控的内部碎片)
  • 提高内外存交换效率(更细的粒度带来了更高的灵活度)
  • 与虚拟内存机制呼应,便于建立虚拟地址->物理地址的映射关系(聚合映射关系的数据结构,称为页表)
  • linux 页/帧的大小固定,为 4KB(这实际是由实践推动的经验值,太粗会增加碎片率,太细会增加分配频率影响效率)

golang的内存管理

基础概念

为了方便自主管理内存, 做法便是先向系统申请一块内存, 然后将内存切割成小块, 通过一定的内存分配算法管理内存。 以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示:
在这里插入图片描述
预申请的内存划分为spans、 bitmap、 arena三部分。 其中arena即为所谓的堆区, 应用中需要的内存从这里分配。其中spans和bitmap是为了管理arena区而存在的。

arena的大小为512G, 为了方便管理把arena区域划分成一个个的page, 每个page为8KB,一共有512GB/8KB个页;

spans区域存放span的指针, 每个指针对应一个page, 所以span区域的大小为(512GB/8KB)*指针大小8byte =512M

bitmap区域大小也是通过arena计算出来, 不过主要用于GC。

内存池概念

在程序中变量的存储位置通常分布在全局数据区、栈区和堆区这三个内存区域:

  • 堆区: 主要承担运行时的动态内存分配任务,它为程序提供灵活的内存使用空间。
  • 全局数据区: 专门用于存储全局变量,这些变量在程序的整个生命周期内都存在。
  • 栈区: 以栈帧为基本单位进行分配,它负责存储函数的参数、返回值以及局部变量。

golang的内存架构

golang的内存分配主要有三个组件
mcache:Per-P(Processer,具体参见go中G,M,P的概念)私有cache,用于实现无锁的object分配。
mcentral:全局内存,为各个cache提供按大小划分好的span,每种对象大小规格(全局共划分为 68 种)对应的缓存,锁的粒度也仅限于同一种规格以内
mheap:全局内存,page管理,内存不足时向系统申请,访问要加全局锁

page和mspan的概念

在此梳理一下span和mspan的概念:

  1. page:最小的存储单元,Golang 借鉴操作系统分页管理的思想,每个最小的存储单元也称之为页 page,但大小为 8 KB。

  2. mspan: 大小为 page 的整数倍,且从 8B 到 80 KB 被划分为 67 种不同的规格,分配对象时,会根据大小映射到不同规格的 mspan,从中获取空间。

在这里插入图片描述

mspan 的数据结构

span是内存管理的基本单位,每个span用于管理特定的class对象, 跟据对象大小, span将一个或多个页拆分成多个块进行管理。源码位于 runtime/mheap.go 文件中:

type mspan struct {// 标识前后节点的指针 next *mspan     prev *mspan    // ...// 起始地址startAddr uintptr // 包含几页,页是连续的npages    uintptr // 标识此前的位置都已被占用 freeindex uintptr// 最多可以存放多少个 objectnelems uintptr // number of object in the span.allocBits *gcBits //分配位图, 每一位代表一个块是否已分配allocCount uint16 // 已分配块的个数// bitmap 每个 bit 对应一个 object 块,标识该块是否已被占用allocCache uint64// ...// 标识 mspan 等级,包含 class 和 noscan 两部分信息spanclass             spanClass   elemsize uintptr // class表中的对象大小, 也即块大小// ...
}

以class 10为例, span和管理的内存如下图所示:
在这里插入图片描述
spanclass为10, 参照class表(下文有)可得出npages=1,nelems=56,elemsize为144。 其中startAddr是在span初始化时就指定了某个页的地址。 allocBits指向一个位图, 每位代表一个块是否被分配, 本例中有两个块已经被分配,其allocCount也为2。next和prev用于将多个span链接起来, 这有利于管理多个span。

mspan 根据空间大小和面向分配对象的大小,被划分为 67 种等级(1-67,实际上还有一种隐藏的 0 级,用于处理更大的对象,上不封顶

// sizeclass
// class  bytes/obj  bytes/span  objects  waste bytes
//     1          8        8192     1024            0
//     2         16        8192      512            0
//     3         32        8192      256            0
//     4         48        8192      170           32
//     5         64        8192      128            0
//     6         80        8192      102           32
//     7         96        8192       85           32
//     8        112        8192       73           16
//     9        128        8192       64            0
//    10        144        8192       56          128
//    11        160        8192       51           32
//    12        176        8192       46           96
//    13        192        8192       42          128
//    14        208        8192       39           80
//    15        224        8192       36          128
//    16        240        8192       34           32
//    17        256        8192       32            0
//    18        288        8192       28          128
//    19        320        8192       25          192
//    20        352        8192       23           96
//    21        384        8192       21          128
//    22        416        8192       19          288
//    23        448        8192       18          128
//    24        480        8192       17           32
//    25        512        8192       16            0
//    26        576        8192       14          128
//    27        640        8192       12          512
//    28        704        8192       11          448
//    29        768        8192       10          512
//    30        896        8192        9          128
//    31       1024        8192        8            0
//    32       1152        8192        7          128
//    33       1280        8192        6          512
//    34       1408       16384       11          896
//    35       1536        8192        5          512
//    36       1792       16384        9          256
//    37       2048        8192        4            0
//    38       2304       16384        7          256
//    39       2688        8192        3          128
//    40       3072       24576        8            0
//    41       3200       16384        5          384
//    42       3456       24576        7          384
//    43       4096        8192        2            0
//    44       4864       24576        5          256
//    45       5376       16384        3          256
//    46       6144       24576        4            0
//    47       6528       32768        5          128
//    48       6784       40960        6          256
//    49       6912       49152        7          768
//    50       8192        8192        1            0
//    51       9472       57344        6          512
//    52       9728       49152        5          512
//    53      10240       40960        4            0
//    54      10880       32768        3          128
//    55      12288       24576        2            0
//    56      13568       40960        3          256
//    57      14336       57344        4            0
//    58      16384       16384        1            0
//    59      18432       73728        4            0
//    60      19072       57344        3          128
//    61      20480       40960        2            0
//    62      21760       65536        3          256
//    63      24576       24576        1            0
//    64      27264       81920        3          128
//    65      28672       57344        2            0
//    66      32768       32768        1            0

在 Golang 中,会将 spanClass + nocan 两部分信息组装成一个 uint8,形成完整的 spanClass 标识. 8 个 bit 中,高 7 位表示了上表的 span 等级(总共 67 + 1 个等级,8 个 bit 足够用了),最低位表示 nocan 信息。代码位于 runtime/mheap.go

type spanClass uint8const (numSpanClasses = _NumSizeClasses << 1tinySpanClass  = spanClass(tinySizeClass<<1 | 1)
)
// uint8 左 7 位为 mspan 等级,最右一位标识是否为 noscan
func makeSpanClass(sizeclass uint8, noscan bool) spanClass {return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}func (sc spanClass) sizeclass() int8 {return int8(sc >> 1)
}func (sc spanClass) noscan() bool {return sc&1 != 0
}

线程缓存 mcache

管理内存的基本单位span, 还要有个数据结构来管理span, 这个数据结构叫mcentral, 各线程需要内存时从mcentral管理的span中申请内存, 为了避免多线程申请内存时不断的加锁, Golang为每个线程分配了span的缓存, 这个缓存即是mcache,代码位于 runtime/mcache.go:

const numSpanClasses = 136
type mcache struct {// 微对象分配器相关tiny       uintptrtinyoffset uintptrtinyAllocs uintptr// mcache 中缓存的 mspan,每种 spanClass 各一个alloc [numSpanClasses]*mspan // ...
}

(1)mcache 是每个 P 独有的缓存,因此交互无锁

(2)mcache 将每种 spanClass 等级的 mspan 各缓存了一个,总数为 2(nocan 维度) * 68(大小维度)= 136

(3)mcache 中还有一个为对象分配器 tiny allocator,用于处理小于 16B 对象的内存分配。
在这里插入图片描述

中心缓存 mcentral

mcache作为线程的私有资源为单个线程服务, 而central则是全局资源, 为多个线程服务, 当某个线程内存不足时会向mcentral申请, 当某个线程释放内存时又会回收进mcentral。代码位于 runtime/mcentral.go

type mcentral struct {// 对应的 spanClassspanclass spanClass// 有空位的 mspan 集合,数组长度为 2 是用于抗一轮 GCpartial [2]spanSet // 无空位的 mspan 集合full    [2]spanSet 
}

(1)每个 mcentral 对应一种 spanClass

(2)每个 mcentral 下聚合了该 spanClass 下的 mspan

(3)mcentral 下的 mspan 分为两个链表,分别为有空间 mspan 链表 partial 和满空间 mspan 链表 full

(4)每个 mcentral 一把锁

全局堆缓存 mheap

从mcentral数据结构可见, 每个mcentral对象只管理特定的class规格的span。 事实上每种class都会对应一个mcentral,这个mcentral的集合存放于mheap数据结构中。代码位于 runtime/mheap.go

type mheap struct {// 堆的全局锁lock mutex// 空闲页分配器,底层是多棵基数树组成的索引,每棵树对应 16 GB 内存空间pages pageAlloc // 记录了所有的 mspan. 需要知道,所有 mspan 都是经由 mheap,使用连续空闲页组装生成的allspans []*mspan// heapAreana 数组,64 位系统下,二维数组容量为 [1][2^22]// 每个 heapArena 大小 64M,因此理论上,Golang 堆上限为 2^22*64M = 256Tarenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena// ...// 多个 mcentral,总个数为 spanClass 的个数central [numSpanClasses]struct {mcentral mcentral// 用于内存地址对齐pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte}// ...
}
  • 对于 Golang 上层应用而言,堆是操作系统虚拟内存的抽象
  • 以页(8KB)为单位,作为最小内存存储单元
  • 负责将连续页组装成 mspan
  • 全局内存基于 bitMap 标识其使用情况,每个 bit 对应一页,为 0 则自由,为 1 则已被 mspan 组装
  • 通过 heapArena 聚合页,记录了页到 mspan 的映射信息(2.7小节展开)
  • 建立空闲页基数树索引 radix tree index,辅助快速寻找空闲页(2.6小节展开)
  • 是 mcentral 的持有者,持有所有 spanClass 下的 mcentral,作为自身的缓存
  • 内存不够时,向操作系统申请,申请单位为 heapArena(64M)

内存分配流程

tiny对象申请

对于tiny对象的申请,mcache中有专门的内存区域“tiny”来进行特殊处理。“tiny”将对象按大小与tinyoffset(“tiny”当前分配地址)对齐,然后分配,并记录下新的tinyoffset,用于下次分配。如果空间不足,则另外申请16 byte的内存块。

noscan := typ == nil || typ.ptrdata == 0// ...if noscan && size < maxTinySize {// tiny 内存块中,从 offset 往后有空闲位置off := c.tinyoffset// ...// 如果当前 tiny 内存块空间还够用,则直接分配并返回if off+size <= maxTinySize && c.tiny != 0 {// 分配空间x = unsafe.Pointer(c.tiny + off)c.tinyoffset = off + sizec.tinyAllocs++mp.mallocing = 0releasem(mp)return x}// ...}

mcache 分配

// 根据对象大小,映射到其所属的 span 的等级(0~66)var sizeclass uint8// get size class ....     // 对应 span 等级下,分配给每个对象的空间大小(0~32KB)// get span classspc := makeSpanClass(sizeclass, noscan) // 获取 mcache 中的 spanspan = c.alloc[spc]  // 从 mcache 的 span 中尝试获取空间        v := nextFreeFast(span)if v == 0 {// mcache 分配空间失败,则通过 mcentral、mheap 兜底            v, span, shouldhelpgc = c.nextFree(spc)}     // 分配空间  x = unsafe.Pointer(v)

在 mspan 中,基于 Ctz64 算法,根据 mspan.allocCache 的 bitMap 信息快速检索到空闲的 object 块,进行返回.

代码位于 runtime/malloc.go 文件中:

func nextFreeFast(s *mspan) gclinkptr {// 通过 ctz64 算法,在 bit map 上寻找到首个 object 空位theBit := sys.Ctz64(s.allocCache) if theBit < 64 {result := s.freeindex + uintptr(theBit)if result < s.nelems {freeidx := result + 1if freeidx%64 == 0 && freeidx != s.nelems {return 0}s.allocCache >>= uint(theBit + 1)// 偏移 freeindex s.freeindex = freeidxs.allocCount++// 返回获取 object 空位的内存地址 return gclinkptr(result*s.elemsize + s.base())}}return 0
}

mcentral 分配

当 mspan 无可用的 object 内存块时,会步入 mcache.nextFree 方法进行兜底.

代码位于 runtime/mcache.go 文件中:

func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {s = c.alloc[spc]// ...// 从 mcache 的 span 中获取 object 空位的偏移量freeIndex := s.nextFreeIndex()if freeIndex == s.nelems {// ...// 倘若 mcache 中 span 已经没有空位,则调用 refill 方法从 mcentral 或者 mheap 中获取新的 span    c.refill(spc)// ...// 再次从替换后的 span 中获取 object 空位的偏移量s = c.alloc[spc]freeIndex = s.nextFreeIndex()}// ...v = gclinkptr(freeIndex*s.elemsize + s.base())s.allocCount++// ...return
}

倘若 mcache 中,对应的 mspan 空间不足,则会在 mcache.refill 方法中,向更上层的 mcentral 乃至 mheap 获取 mspan,填充到 mache 中:

代码位于 runtime/mcache.go 文件中:

func (c *mcache) refill(spc spanClass) {  s := c.alloc[spc]// ...// 从 mcentral 当中获取对应等级的 spans = mheap_.central[spc].mcentral.cacheSpan()// ...// 将新的 span 添加到 mcahe 当中c.alloc[spc] = s
}

mcentral.cacheSpan 方法中,会加锁(spanClass 级别的 sweepLocker),分别从 partial 和 full 中尝试获取有空间的 mspan:

代码位于 runtime/mcentral.go 文件中:

func (c *mcentral) cacheSpan() *mspan {// ...var sl sweepLocker    // ...sl = sweep.active.begin()if sl.valid {for ; spanBudget >= 0; spanBudget-- {s = c.partialUnswept(sg).pop()// ...if s, ok := sl.tryAcquire(s); ok {// ...sweep.active.end(sl)goto havespan}// 通过 sweepLock,加锁尝试从 mcentral 的非空链表 full 中获取 mspanfor ; spanBudget >= 0; spanBudget-- {s = c.fullUnswept(sg).pop()// ...if s, ok := sl.tryAcquire(s); ok {// ...sweep.active.end(sl)goto havespan}// ...}}// ...}// ...// 执行到此处时,s 已经指向一个存在 object 空位的 mspan 了
havespan:// ...return
}

mheap 分配

在 mcentral.cacheSpan 方法中,倘若从 partial 和 full 中都找不到合适的 mspan 了,则会调用 mcentral 的 grow 方法,将事态继续升级:

func (c *mcentral) cacheSpan() *mspan {// ...// mcentral 中也没有可用的 mspan 了,则需要从 mheap 中获取,最终会调用 mheap_.alloc 方法s = c.grow()// ...// 执行到此处时,s 已经指向一个存在 object 空位的 mspan 了
havespan:// ...return
}

经由 mcentral.grow 方法和 mheap.alloc 方法的周转,最终会步入 mheap.allocSpan 方法中:

func (c *mcentral) grow() *mspan {npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])size := uintptr(class_to_size[c.spanclass.sizeclass()])s := mheap_.alloc(npages, c.spanclass)// ...// ...return s
}

代码位于 runtime/mheap.go

func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan {var s *mspansystemstack(func() {// ...s = h.allocSpan(npages, spanAllocHeap, spanclass)})return s
}

代码位于 runtime/mheap.go

func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {gp := getg()base, scav := uintptr(0), uintptr(0)// ...此处实际上还有一阶缓存,是从每个 P 的页缓存 pageCache 中获取空闲页组装 mspan,此处先略去了...// 加上堆全局锁lock(&h.lock)if base == 0 {// 通过基数树索引快速寻找满足条件的连续空闲页base, scav = h.pages.alloc(npages)// ...}// ...unlock(&h.lock)HaveSpan:// 把空闲页组装成 mspans.init(base, npages)// 将这批页添加到 heapArena 中,建立由页指向 mspan 的映射h.setSpans(s.base(), npages, s)// ...return s
}

向操作系统申请

倘若 mheap 中没有足够多的空闲页了,会发起 mmap 系统调用,向操作系统申请额外的内存空间.

代码位于 runtime/mheap.go 文件的 mheap.grow 方法中:

func (h *mheap) grow(npage uintptr) (uintptr, bool) {av, asize := h.sysAlloc(ask)
}
func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer, size uintptr) {v = sysReserve(unsafe.Pointer(p), n)
}
func sysReserve(v unsafe.Pointer, n uintptr) unsafe.Pointer {return sysReserveOS(v, n)
}
func sysReserveOS(v unsafe.Pointer, n uintptr) unsafe.Pointer {p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)if err != 0 {return nil}return p
}

mallocgc源码解读

代码位于 runtime/malloc.go 文件中:

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {// ...    // 获取 mmp := acquirem()// 获取当前 p 对应的 mcachec := getMCache(mp)var span *mspanvar x unsafe.Pointer// 根据当前对象是否包含指针,标识 gc 时是否需要展开扫描noscan := typ == nil || typ.ptrdata == 0// 是否是小于 32KB 的微、小对象if size <= maxSmallSize {// 小于 16 B 且无指针,则视为微对象if noscan && size < maxTinySize {// tiny 内存块中,从 offset 往后有空闲位置off := c.tinyoffset// 如果大小为 5 ~ 8 B,size 会被调整为 8 B,此时 8 & 7 == 0,会走进此分支if size&7 == 0 {// 将 offset 补齐到 8 B 倍数的位置off = alignUp(off, 8)// 如果大小为 3 ~ 4 B,size 会被调整为 4 B,此时 4 & 3 == 0,会走进此分支  } else if size&3 == 0 {// 将 offset 补齐到 4 B 倍数的位置off = alignUp(off, 4)// 如果大小为 1 ~ 2 B,size 会被调整为 2 B,此时 2 & 1 == 0,会走进此分支  } else if size&1 == 0 {// 将 offset 补齐到 2 B 倍数的位置off = alignUp(off, 2)}
// 如果当前 tiny 内存块空间还够用,则直接分配并返回if off+size <= maxTinySize && c.tiny != 0 {// 分配空间x = unsafe.Pointer(c.tiny + off)c.tinyoffset = off + sizec.tinyAllocs++mp.mallocing = 0releasem(mp)  return x} // 分配一个新的 tiny 内存块span = c.alloc[tinySpanClass]    // 从 mCache 中获取v := nextFreeFast(span)        if v == 0 {// 从 mCache 中获取失败,则从 mCentral 或者 mHeap 中获取进行兜底v, span, shouldhelpgc = c.nextFree(tinySpanClass)}   
// 分配空间      x = unsafe.Pointer(v)(*[2]uint64)(x)[0] = 0(*[2]uint64)(x)[1] = 0size = maxTinySize} else {// 根据对象大小,映射到其所属的 span 的等级(0~66)var sizeclass uint8if size <= smallSizeMax-8 {sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]} else {sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]}        // 对应 span 等级下,分配给每个对象的空间大小(0~32KB)size = uintptr(class_to_size[sizeclass])// 创建 spanClass 标识,其中前 7 位对应为 span 的等级(0~66),最后标识表示了这个对象 gc 时是否需要扫描spc := makeSpanClass(sizeclass, noscan) // 获取 mcache 中的 spanspan = c.alloc[spc]  // 从 mcache 的 span 中尝试获取空间        v := nextFreeFast(span)if v == 0 {// mcache 分配空间失败,则通过 mcentral、mheap 兜底            v, span, shouldhelpgc = c.nextFree(spc)}     // 分配空间  x = unsafe.Pointer(v)// ...}      // 大于 32KB 的大对象      } else {// 从 mheap 中获取 0 号 spanspan = c.allocLarge(size, noscan)span.freeindex = 1span.allocCount = 1size = span.elemsize         // 分配空间   x = unsafe.Pointer(span.base())}  // ...return x
}

内存分配过程总结

针对待分配对象的大小不同有不同的分配逻辑:

  • (0, 16B) 且不包含指针的对象: Tiny分配
  • (0, 16B) 包含指针的对象: 正常分配
  • [16B, 32KB] : 正常分配
  • (32KB, -) : 大对象分配其中Tiny分配和大对象分配都属于内存管理的优化范畴, 这里暂时仅关注一般的分配方法。

以申请size为n的内存为例, 分配步骤如下:

  1. 获取当前线程的私有缓存mcache
  2. 跟据size计算出适合的class的ID
  3. 从mcache的alloc[class]链表中查询可用的span
  4. 如果mcache没有可用的span则从mcentral申请一个新的span加入mcache中
  5. 如果mcentral中也没有可用的span则从mheap中申请一个新的span加入mcentral
  6. 从该span中获取到空闲对象地址并返回

编程小Tips

首先看下哪些对象会分配到堆上?

  1. 堆上的指针只能指向堆上的对象
  2. interface、chan、map所持有或转化为interface的对象都会在堆上
  3. 函数内返回指针类型的,指针指向的对象会逃逸到堆上。
  4. string类型一定在堆上
  5. 切片扩容和拷贝

原则上,减少GC就是减少在堆上分配对象。

  1. 切片和map知道大小的,预先分配足够的大小,避免扩容。

  2. 尽量少生成string,使用切片的方式复用string。

  3. 少用反射,被反射的对象都会分配到堆上。

  4. 使用sync.Pool复用对象,避免重复生产销毁对象。

  5. 尽量让chan传递较小的对象,所有chan传递的对象都会分配到堆上。

第二个方法:调节GC频率

通过debug.SetGCPercent或环境变量GOGC设置堆空间增长率p,只有当堆空间达到上次GC完成时堆空间大小*(1+p)才会触发GC。

通过设置debug.SetMemoryLimit或环境变量GOMEMLIMIT设置软限制内存限制,这个条件会使go的GC频率动态调节,用于达到GOMEMLIMIT的需要,可用于防止OOM。但不会强制程序的内存使用量。

参考文档

Golang 内存模型与分配机制 - 小徐先生的文章 - 知乎
go 内存管理和GC详解 - 卡萨布兰卡的文章 - 知乎
操作系统虚拟内存管理(二):虚拟内存管理 - Mulily的文章 - 知乎
Golang内存管理(一):堆内存管理 - Mulily的文章 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git 第一次安装设置用户名密码

git config --global user.name ljq git config --global user.email 15137659164qq.com创建公钥命令 输入后一直回车 ssh-keygen -t rsa下面这样代表成功 这里是公钥的 信息输入gitee 中 输入下面命令看是否和本机绑定成功 ssh -T gitgitee.com如何是这样&#xff0c;恭喜…

基于51单片机PWM控制直流电机—数码管显示

基于51单片机PWM控制直流电机 &#xff08;仿真&#xff0b;程序&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.L298驱动直流电机&#xff1b; 2.数码管显示转动方向和PWM占空比&#xff08;0-100%&#xff09;&#xff1b; 3.按键控制PWM占空比来加/…

20232803 2023-2024-2 《网络攻防实践》实践八报告

目录 1. 实践内容2. 实践过程2.1 动手实践任务一2.2 动手实践任务二&#xff1a;分析Crackme程序2.2.1 crackme1.exe2.2.2 crackme2.exe 2.3 分析实践任务一2.4 分析实践任务二 3. 学习中遇到的问题及解决4. 学习感悟、思考等 1. 实践内容 动手实践任务一&#xff1a;对提供的r…

R语言实战——中国职工平均工资的变化分析——相关与回归分析

链接: R语言学习—1—将数据框中某一列数据改成行名 R语言学习—2—安德鲁斯曲线分析时间序列数据 R语言学习—3—基本操作 R语言学习—4—数据矩阵及R表示 R语言的学习—5—多元数据直观表示 R语言学习—6—多元相关与回归分析 1、源数据 各行业平均工资变化 各地区平均工资…

list 的模拟实现

目录 1. list 的实现框架 2. push_back 3. 迭代器 4. constructor 4.1. default 4.2. fill 4.3. range 4.4. initializer list 5. insert 6. erase 7. clear 和 destructor 8. copy constructor 9. operator 10. const_iterator 10.1. 普通人的处理方案 10.2. …

数据库复习1

1.试述数据、数据库、数据库管理系统、数据库系统的概念 1.数据(Data): 数据是关于事物的符号表示或描述。它可以是任何事实、观察或者测量的结果&#xff0c;如数字、字符、声音、图像等。数据在没有上下文的情况下可能没有明确的意义。 2.数据库(Database): 数据库是一个持…

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信&#xff0c;了解到了socket的概念与udp的实现方法&#xff0c;今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字&#xff08;socket&#xff09;来实现通信的&#xff0c;因此TCP也得使用socket()接口创建…

时间复杂度_空间复杂度

时间复杂度_空间复杂度 1.算法效率 算法效率分析分为两种:第一种是时间效率&#xff0c;第二种是空间效率。 时间效率被称为时间复杂度&#xff0c;而空间效率被称作空间复杂度。时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空间复杂度主要衡量一个算法所需要的…

C#技巧之同步与异步

区别 首先&#xff0c;同步就是程序从上往下顺序执行&#xff0c;要执行完当前流程&#xff0c;才能往下个流程去。 而异步&#xff0c;则是启动当前流程以后&#xff0c;不需要等待流程完成&#xff0c;立刻就去执行下一个流程。 同步示例 创建一个窗体&#xff0c;往窗体里…

2131 - 枚举-练习-涂国旗

2131 - 枚举-练习-涂国旗 c刷题 超能力编程 分析 枚举涂w的底边和涂b的底边即可 剩下的部分都涂r 数据范围这么小,暴力枚举&#xff0c;代码简单难度低。搜索什么的用不着啦&#xff01; 那么问题来了&#xff1a;怎么枚举呢&#xff1f; 我们只要枚举白与蓝、蓝与红的边界&…

【DPU系列之】DPU中的ECPF概念是什么?全称是什么?(E CPF对标H CPF;embedded CPU function ownership)

ECPF&#xff1a;embedded CPU function ownership。 嵌入式CPU运转ownership。也叫DPU模式&#xff0c;是DPU工作运转3种模式之一&#xff0c;也是默认的模式。这里的嵌入式CPU指的是DPU上ARM CPU&#xff0c;表示网卡所有资源和功能被embedded CPU全权管理&#xff0c;行使所…

【动态规划】投资问题

本文利用markdown基于https://blog.csdn.net/qq_41926985/article/details/105627049重写,代码部分为本人编辑 代码要求 应用动态规划方法&#xff0c;求解投资问题&#xff0c;实现下面的例子。 #define MAX_N 4 //最大投资项目数目 #define MAX_M 5 //最大投资钱数(万元) /…

【机器视觉】yolo-world-opencvsharp-.net4.8 C# 窗体应用程序

这段代码是基于 OpenCvSharp, OpenVinoSharp 和 .NET Framework 4.8 的 Windows Forms 应用程序。其主要目的是加载和编译机器学习模型&#xff0c;对输入数据进行推理&#xff0c;并显示结果。 下面是该程序的主要功能和方法的详细总结&#xff1a; 初始化 OpenVINO 运行时核心…

基于Pytorch深度学习——卷积神经网络(卷积层/池化层/多输入多输出通道/填充和步幅/)

本文章来源于对李沐动手深度学习代码以及原理的理解&#xff0c;并且由于李沐老师的代码能力很强&#xff0c;以及视频中讲解代码的部分较少&#xff0c;所以这里将代码进行尽量逐行详细解释 并且由于pytorch的语法有些小伙伴可能并不熟悉&#xff0c;所以我们会采用逐行解释小…

【DPU系列之】如何通过带外口登录到DPU上的ARM服务器?(Bluefield2举例)

文章目录 1. 背景说明2. 详细操作步骤2.1 目标拓扑结构2.2 连接DPU带外口网线&#xff0c;并获取IP地址2.3 ssh登录到DPU 3. 进一步看看系统的一些信息3.1 CPU信息&#xff1a;8核A723.2 内存信息 16GB3.3 查看ibdev设备 3.4 使用小工具pcie2netdev查看信息3.5 查看PCIe设备信息…

python笔记:gensim进行LDA

理论部分&#xff1a;NLP 笔记&#xff1a;Latent Dirichlet Allocation &#xff08;介绍篇&#xff09;-CSDN博客 参考内容&#xff1a;DengYangyong/LDA_gensim: 用gensim训练LDA模型&#xff0c;进行新闻文本主题分析 (github.com) 1 导入库 import jieba,os,re from ge…

【云原生】Docker 的网络通信

Docker 的网络通信 1.Docker 容器网络通信的基本原理1.1 查看 Docker 容器网络1.2 宿主机与 Docker 容器建立网络通信的过程 2.使用命令查看 Docker 的网络配置信息3.Docker 的 4 种网络通信模式3.1 bridge 模式3.2 host 模式3.3 container 模式3.4 none 模式 4.容器间的通信4.…

Stream流操作

看到Stream流这个概念&#xff0c;我们很容易将其于IO流联系在一起&#xff0c;事实上&#xff0c;两者并没有什么关系&#xff0c;IO流是用于处理数据传输的&#xff0c;而Stream流则是用于操作集合的。 当然&#xff0c;为了方便我们区分&#xff0c;我们依旧在这里复习一下…

长期找 AI 专家,邀请参加线上聊天直播

诚邀 AI 专家参加线上聊天&#xff0c;成为嘉宾。 分享前沿观点、探讨科技和生活 除节假日外&#xff0c;每周举办在线聊天直播 根据话题和自愿形式结合&#xff0c;每期 2~3 位嘉宾 成为嘉宾&#xff0c;见下&#xff1a;

ADS软件(PathWave 先进设计系统软件)分享与安装

ADS软件的简介 ADS软件&#xff08;Advanced Design System&#xff09;主要用于射频&#xff08;RF&#xff09;、微波&#xff08;Microwave&#xff09;和毫米波&#xff08;Millimeter-wave&#xff09;电路的设计、仿真和分析。它提供了一套强大的工具和功能&#xff0c;…