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.rhkb.cn/news/319696.html

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

相关文章

【机器学习-21】集成学习---Bagging之随机森林(RF)

【机器学习】集成学习---Bagging之随机森林&#xff08;RF&#xff09; 一、引言1. 简要介绍集成学习的概念及其在机器学习领域的重要性。2. 引出随机森林作为Bagging算法的一个典型应用。 二、随机森林原理1. Bagging算法的基本思想2. 随机森林的构造3. 随机森林的工作机制 三…

JVM的垃圾回收机制(GC机制)

在Java代码运行的过程中&#xff0c;JVM发现 某些资源不需要再使用的时候&#xff0c;就会自动把资源所占的内存给回收掉&#xff0c;就不需要程序员自行操作了。“自动回收资源”就是JVM的“垃圾回收机制”&#xff0c;“垃圾回收机制”也称"GC机制"。 对于Java代码…

【论文笔记】Training language models to follow instructions with human feedback A部分

Training language models to follow instructions with human feedback A 部分 回顾一下第一代 GPT-1 &#xff1a; 设计思路是 “海量无标记文本进行无监督预训练少量有标签文本有监督微调” 范式&#xff1b;模型架构是基于 Transformer 的叠加解码器&#xff08;掩码自注意…

OceanBase开发者大会实录-杨传辉:携手开发者打造一体化数据库

本文来自2024 OceanBase开发者大会&#xff0c;OceanBase CTO 杨传辉的演讲实录—《携手开发者打造一体化数据库》。完整视频回看&#xff0c;请点击这里&#xff1e;> 各位 OceanBase 的开发者&#xff0c;大家上午好&#xff01;今天非常高兴能够在上海与大家再次相聚&…

半监督节点分类:标签传播和消息传递

基础概念回顾 传统图机器学习的特征工程——节点层面&#xff0c;连接层面&#xff0c;全图层面 节点层面&#xff1a;信用卡欺诈 连接层面&#xff1a;推荐可能认识的人 全图层面&#xff1a;预测分子结构 半监督节点分类 半监督节点分类&#xff1a;用已知标签节点预测未…

路由器的构成

一、路由器简介 路由器是互联网中的关键设备&#xff1a; 连接不同的网络路由器是多个输入端口和多个输出端口的专用计算机&#xff0c;其任务是转发分组&#xff08;转发给下一跳路由器&#xff09;下一跳路由器也按照这种方法处理分组&#xff0c;直到该分组到达终点为止 …

Gone框架介绍3 - 使用gone命令,自动生成Priest函数

文章目录 1. 安装辅助工具: gone2. 创建一个名为gen-code的新项目3. 创建Goner4. 使用辅助工具5. 添加main函数 我在两年前实现了一个Golang的依赖注入框架&#xff0c;并且集成了gin、xorm、redis、cron、消息中间件等功能&#xff0c;自己觉得还挺好用的&#xff1b;之前一直…

js api part3

环境对象 环境对象&#xff1a; 指的是函数内部特殊的 变量 this &#xff0c; 它代表着当前函数运行时所处的环境 作用&#xff1a; 弄清楚this的指向&#xff0c;可以让我们代码更简洁 函数的调用方式不同&#xff0c;this 指代的对象也不同 【谁调用&#xff0c; this 就是…

中科院突破:TalkingGaussian技术实现3D人脸动态无失真,高效同步嘴唇运动!

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;探索高质量3D对话头像的新方法 在数字媒体和虚拟互动领域&#xff0c;高质量的3D对话头像技术正变得日益重要。这种技术能够在虚拟现实、电影…

银河麒麟桌面版开机后网络无法自动链接 麒麟系统开机没有连接ens33

1.每次虚拟机开机启动麒麟操作系统&#xff0c;都要输入账号&#xff0c;密码。 进入点击这个ens33 内网才连接 2. 如何开机就脸上呢&#xff1f; 2.1. 进入 cd /etc/sysconfig/network-scripts 2.2 修改参数 onbootyes 改为yes 2.3 重启即可 a. 直接重启机器查看是否正常&…

docker原理

Docker原理 在前面我们学习了Docker&#xff0c;接下来我们探究一下Docker的底层技术原理 Linux 命名空间&#xff08;namespace&#xff09;、控制组&#xff08;cgroups&#xff09;和 联合文件系统&#xff08;UnionFS&#xff09; 三大技术支撑了目前 Docker 的实现&…

Unity SteamVR入门

概述 VR项目现在在当前已经是非常热门的技术&#xff0c;可以给玩家身临其境的感觉&#xff0c;接下来让我们学习这部分的内容吧&#xff01; SteamVR Input SteamVR绑定流程&#xff0c;在Windows窗口的点击SteamVR-input&#xff0c;图1&#xff0c;在这里可以选择你需要绑定…

2024五一杯数学建模C题思路分享 - 煤矿深部开采冲击地压危险预测

文章目录 1 赛题选题分析 2 解题思路2.1 问题重述2.2 第一问完整思路2.2 二、三问思路更新 3 最新思路更新 1 赛题 C题 煤矿深部开采冲击地压危险预测 煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动…

【Flask 系统教程 4】Jinjia2模版和语法

Jinjia2 模板 模板的介绍 Jinja2 是一种现代的、设计优雅的模板引擎&#xff0c;它是 Python 的一部分&#xff0c;由 Armin Ronacher 开发。Jinja2 允许你在 HTML 文档中嵌入 Python 代码&#xff0c;以及使用变量、控制结构和过滤器来动态生成内容。它的语法简洁清晰&#…

kerberos-hive-dbeaver问题总结

一、kerberos安装windows客户端 1、官方下载地址 http://web.mit.edu/kerberos/dist/ 2、环境变量配置 下载msi安装包&#xff0c;无需重启计算机&#xff0c;调整环境变量在jdk的前面&#xff0c;尽量靠前&#xff0c;因为jdk也带了kinit、klist等命令 C:\Program Files\…

【多模态大模型】AI对视频内容解析问答

文章目录 1. 项目背景2. 直接对视频进行解析进行AI问答&#xff1a;MiniGPT4-Video2.1 MiniGPT4-Video效果 3. 对视频抽帧为图片再进行AI问答3.1 视频抽帧3.2 图片AI问答3.2.1 阿里通义千问大模型 Qwen-vl-plus3.2.2 Moonshot 1. 项目背景 最近在做一个项目,需要使用AI技术对视…

Git常用(持续更新)

常用场景&#xff1a; 初始化&#xff1a; git config --global user.name "codelabs" git config --global user.email mycodelabs.com git init git remote add origin https://github.com/username/repository.git git pull origin master 提交&#xff1a; gi…

【Android学习】自定义文本框和输入监听

实现功能 以上代码可实现功能&#xff1a; 1 自定义文本框样式 2. 文本框触发形式转变 3. 文本框输入长度监听&#xff0c;达到最大长度关闭软键盘 4. password框触发检测phone框内容 1. drawable自定义形状 我创建了editor_focus.xml 和 editor_unfocus.xml&#xff0c;两者仅…

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…

2024年 Java 面试八股文——SpringBoot篇

目录 1. 什么是 Spring Boot&#xff1f; 2. 为什么要用SpringBoot 3. SpringBoot与SpringCloud 区别 4. Spring Boot 有哪些优点&#xff1f; 5. Spring Boot 的核心注解是哪个&#xff1f;它主要由哪几个注解组成的&#xff1f; 6. Spring Boot 支持哪些日志框架&#…