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

news/2024/5/19 6:45:24

操作系统的存储管理

虚拟内存管理

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

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

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

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

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

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

进程的虚拟内存管理

每个进程的虚拟内存空间都会映射到物理内存,如何映射对于程序员来说是透明的不需要关心,虚拟内存到物理内存的映射由操作系统实现。内核态虚拟内存空间是所有进程共享的,不同进程进入内核态之后看到的虚拟内存空间全部是一样的。比如下图中进程 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.mrgr.cn/p/04424727

相关文章

多器官和多模态图像的通用异常检测模型-不受特定模型约束

文章目录 A Model-Agnostic Framework for Universal Anomaly Detection of Multi-organ and Multi-modal Images摘要方法实验结果 A Model-Agnostic Framework for Universal Anomaly Detection of Multi-organ and Multi-modal Images 摘要 背景与挑战&#xff1a;深度学习在…

anaconda下载keras包

——anaconda prompt中下载 打开anaconda prompt,切换到/激活对应的环境 通过 conda install -c conda-forge keras 安装 keras 通过 pip list 查看现有列表确认是否安装成功——pycharm中下载 点击python软件包,输入需要下载的软件包,选择版本后安装即可

仿知乎网站问答源码,开源版

仿知乎网站问答源码&#xff0c;开源版 需要一定动手能力 发文章&#xff0c;发视频&#xff0c;发想法&#xff0c;提问回答&#xff0c;注册登录 开发环境 使用技术&#xff1a;springbootthymeleafRedis&#xff1b; 开发环境&#xff1a;tomcat8.0&#xff0c;jdk8.0, ID…

DevOps,CI,CD,自动化简单介绍

原创 追逐时光者前言随着企业应用的不断迭代和发展,应用的版本发布可能涉及了多个技术团队(如PC端,移动端,小程序端等)。随之而来的问题是应用发布也成为了一项高风险,高压力的操作过程。并且应用的开发迭代的沟通,测试成本也大大的变得不可控。这时候就出现了DevOps管…

java中http调用组件深入详解

目录 一、前言 二、http调用概述 2.1 什么是http调用 2.1.1 http调用步骤 2.2 HTTP调用特点 2.3 HTTP调用应用场景 三、微服务场景下http调用概述 3.1 微服务开发中http调用场景 3.2 微服务组件中http的应用 四、常用的http调用组件 4.1 java中常用的http组件介绍 4…

数据结构===二叉树

文章目录 概要二叉树的概念分类存储遍历前序中序后序 小结 概要 简单写下二叉树都有哪些内容&#xff0c;这篇文章要写什么 二叉树的概念分类&#xff0c;都有哪些二叉树遍历 对一个数据结构&#xff0c;最先入手的都是定义&#xff0c;然后才会有哪些分类&#xff0c;对二叉…

File contains parsing errors: file:///etc/yum.repos.d/nginx.repo报错解决,文件配置出现问题

执行yum指令出现以下错误&#xff1a; 解决方案&#xff1a;yum的配置文件出现问题&#xff0c; 先删除yum.repos.d目录下所有文件 rm -f /etc/yum.repos.d/* 然后重新下载阿里的资源 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.…

【Mac】Lightroom Classic 2024 v13.1安装教程

软件介绍 Lightroom Classic 2024是Adobe公司推出的一款专业的数字图像处理软件&#xff0c;旨在为摄影师提供强大的工具和功能&#xff0c;以管理、编辑和分享他们的照片作品。以下是Lightroom Classic 2024的主要特点和功能&#xff1a; 数字照片管理&#xff1a; 提供直观…

SpringData JPA - ORM 框架下,打造高效数据访问层

目录 一、SpringData JPA 概述 1.1、什么是 JPA 1.2、什么是 ORM 1.3、什么是 Hibernate 1.4、JPA 和 Hibernate 的关系 1.5、JPA 的优势 二、SpringData JPA 实战开发 2.1、依赖 2.2、配置文件 2.3、启动类 2.4、创建实体 2.5、基于 JpaRepository 的 CRUD 三、…

【软件设计师】上午题

【软考】软件设计师plus 「软件设计师」 2022年下半年上午真题解析视频 计算机系统知识 22下 考点&#xff1a;指令系统之CISC vs RISC RISC指令系统整体特点是简单、精简 》指令种类少&#xff0c;但是指令功能强 考点&#xff1a;计算机系统组成 A属于运算器&#xff0c;…

图文并茂手把手教你安装windows搭建sqlserver(SqlServer数据库安装保姆级教程)

sqlserver是什么 SQL Server 是由美国微软公司(Microsoft)开发的一款关系型数据库管理系统(RDBMS)。作为全球主流的数据库平台之一,SQL Server 设计用于高效管理和处理大量结构化数据,支持各种规模的应用场景,从个人电脑到企业级数据中心,乃至云端部署。 SQL Server 数…

FineBI学习:K线图

效果图 底表结构&#xff1a;日期、股票代码、股票名称、开盘价、收盘价、最高价、最低价 步骤&#xff1a; 横轴&#xff1a;日期 纵轴&#xff1a;开盘价、最低价 选择【自定义图表】&#xff0c;或【瀑布图】 新建字段&#xff1a;价差&#xff08;收盘-开盘&#xf…

Spring IoCDI (1)

目录 一、IoC & DI入门 1、Spring是什么 &#xff08;1&#xff09;什么是容器&#xff1f; &#xff08;2&#xff09;什么是IoC&#xff1f; 二、IoC介绍 1、传统程序开发 2、解决方案 3、IoC程序开发 4、IoC优势 三、DI介绍 通过前面的学习&#xff0c;我们知…

Excel 批量获取sheet页名称,并创建超链接指向对应sheet页

参考资料 用GET.WORKBOOK函数实现excel批量生成带超链接目录且自动更新 目录 一. 需求二. 名称管理器 → 自定义获取sheet页名称函数三. 配合Index函数&#xff0c;获取所有的sheet页名称四. 添加超链接&#xff0c;指向对应的sheet页 一. 需求 ⏹有如下Excel表&#xff0c;需…

BI工具选型不入坑,你要这么选

✅作者简介&#xff1a;《数据运营&#xff1a;数据分析模型撬动新零售实战》作者、《数据实践之美》作者、数据科技公司创始人、多次参加国家级大数据行业标准研讨及制定、高端企培合作讲师。 &#x1f338;公众号&#xff1a;风姑娘的数字视角&#xff0c;免费分享数据应用相…

tomcat启动闪退问题解决方法

文章目录 Tomcat启动闪退的常见问题及解决方法Tomcat是什么启动闪退的常见问题及解决方法1. 内存不足问题描述&#xff1a;案例分析&#xff1a;解决方法&#xff1a; 2. 端口冲突问题描述&#xff1a;解决方法&#xff1a; 3. 日志错误问题描述&#xff1a;解决方法&#xff1…

基于JSP的人才公寓管理系统

目录 背景 技术简介 系统简介 界面预览 背景 随着互联网的广泛推广和应用&#xff0c;人才公寓管理系统在网络技术的推动下迅速进步。该系统的设计初衷是满足住户的实际需求&#xff0c;通过深入了解住户的期望&#xff0c;开发出高度定制化的人才公寓管理系统。利用互联网…

docker-compose启动mysql5.7报错

描述一下问题经过&#xff1a; 使用docker compose 部署mysql5.7 文件如下: 使用命名卷的情况下&#xff0c;匿名卷不存在该问题 services:mysql:restart: alwaysimage: mysql:5.7container_name: mysql-devports:- 3306:3306environment:- MYSQL_DATABASEdev- MYSQL_ROOT_PAS…

500行代码实现贪吃蛇(1)

文章目录 目录1. Win32 API 介绍1.1 Win32 API1.2 控制台程序&#xff08;Console&#xff09;1.3 控制台屏幕上的坐标COORD1.4 [GetStdHandle](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle)1.5 [GetConsoleCursorInfo](https://learn.microsoft.com/zh-c…

绿丰源创投实世纪:科技与环保融合的领军团队

绿丰源创投实世纪是一支专注于科技与环保融合领域的卓越团队&#xff0c;我们汇聚了一群拥有丰富经验、深厚专业背景和卓越才华的精英。 1. 精英团队&#xff1a; 我们的团队由金融领域的投资经理、科技界的领军人物、环保行业的精英等多领域专业人士组成。每位成员都拥有多年…