go 手动释放内存_golang 内存管理

本文探讨了Go语言内存管理的细节,包括如何手动释放内存。内容涵盖虚拟内存与物理内存的概念,简单的内存分配方法如线性分配和链表分配器,以及Go语言借鉴TCMalloc实现的内存分配策略。Go将内存分为微对象、小对象和大对象,使用mcache、mcentral和mheap等数据结构进行高效管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、虚拟内存、物理内存和页表

b3b69684b88fe33f5fbf403992169739.png

每一个进程都有自身的虚拟内存,通过页表将虚拟内存映射到物理内存。虚拟内存空间被内核划分为了等长的部分,这部分称之为,物理内存也被划分为了同样大小的页,通常称之为页帧

fa6281983b5c2cd335bf06ecf7227354.png

不同的虚拟内存空间中的页可以映射到同一个物理内存页,内核可以决定哪些内存区域在进程间共享,哪些不共享;不是所有的虚拟空间页都会映射到物理内存,虚拟空间远大于物理内存。

用来将虚拟地址空间映射到物理地址空间的数据结构称为页表,实际中为了提高性能、节省空间往往采用多级页表,linux 采用了四级页表。

虚拟内存中的栈主要保存函数的参数、返回值和局部变量等,由编译器管理;用户主动申请的内存主要保存在堆中,由用户和编译器共同管理,并且堆中内存需要垃圾回收机制,java、go、python等语言本身会负责垃圾回收,c、c++等需要用户自己负责内存的申请和释放。

2、简单的内存分配方法

  1. 线性分配
    维护一个指向内存的指针,移动指针实现内存的分配

46e3b95553277378b9e6736f368fabf1.png


实现复杂度低,内存分配快,但需要不停拷贝、合并内存以实现内存释放和回收

  1. 链表分配器
    维护一个空闲内存的链表

d1c13448e24404e6910218048b9fbd64.png


实现复杂度低,内存的释放和回收简单,但是需要遍历链表,为了进一步提高效率,可以把内存分割成不同的大小,分别组成不同的链表,根据申请内存的大小使用相应的链表

3、TCMalloc

线程缓存分配(TCMalloc,thread-caching malloc)是一种快速、高效的内存分配方法,其核心思想是将内存划分为不同的级别,以减少锁竞争,TCMalloc 将内存划分为了 线程缓存(thread-cache)和页堆(page-heap)两个部分。

Thread Cache

每个线程都拥有一个线程缓存,不需要锁,线程缓存由不同的固定大小链表组成,根据对象的大小选择合适的链表分配内存,减少空间碎片化,小于 32K 的内存将在线程缓存分配。

d6c8a4277149436d0913568ed48f8ff5.png

Page Heap

对于超过 32K 的内存将由页堆直接分配,每个页堆包含不同大小的页集合,span 表示一组连续的页,包含页的起始地址(start)和页数(n_pages)。当页堆空间不足时将向操作系统申请更多的内存空间。

a1de6707b8aeeeb91d9624effab0505a.png

4、GO 内存分配

Go 的内存分配方式借鉴了 TCMalloc,根据对象的大小将对象分成了微对象、小对象和大对象三种:

| 类别 | 大小 |

| ------ | :---------- |

| 微对象 | (0, 16B) |

| 小对象 | [16B, 32KB] |

| 大对象 | (32KB, +∞) |

其中微对象使用微对象内存分配器,小对象使用线程缓存中心缓存,大对象使用页堆,具体,Go 将 32K 大小的内存对象划分为了 67 类,如下所示:

https://sourcegraph.com/github.com/golang/go@master/-/blob/src/runtime/sizeclasses.go#L83:1

 1// class 大小: 0 -> 32K
 2var class_to_size = [_NumSizeClasses]uint16{
 3    0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 
 4    208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 
 5    576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792,
 6    2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 
 7    6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 
 8    14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768,
 9}
10
11// class 所需页数
12var class_to_allocnpages = [_NumSizeClasses]uint8{
13    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
14    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1,
15    3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 
16    7, 5, 8, 3, 10, 7, 4,
17}

例如,对于 1024B 的对象将使用 1 个页(8192B),则将页划分为了 8 个相同大小的区域(1024B):

dcb7e7f0aa9913410e0882efb56d8b70.png

mspan

mspan 是 go 内存管理的基本单元,也是组织 pages 的数据结构,多个 mspan 组成一个双向链表:

https://sourcegraph.com/github.com/golang/go@619072be4138e3fc092a9b77d57a9abc5333a4ab/-/blob/src/runtime/mheap.go#L402:6

 1type mspan struct {
 2    next *mspan     // next span in list, or nil if none
 3    prev *mspan     // previous span in list, or nil if none
 4
 5    startAddr uintptr // address of first byte of span aka s.base()
 6    npages    uintptr // number of pages in span
 7
 8    spanclass   spanClass     // size class and noscan (uint8)
 9
10    ...
11}

508135f4b851d12d013163ab05a234c8.png

mcache

mcache 是 go 的线程缓存,与逻辑处理器 P 绑定,每个 mcache 持有 67*2 个 mspan(scan + noscan)(保存在 alloc 中),用来存储微小对象:

https://sourcegraph.com/github.com/golang/go@619072be4138e3fc092a9b77d57a9abc5333a4ab/-/blob/src/runtime/mcache.go

1type mcache struct {
2    alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
3
4    ...
5}

47731852a3f15bc17fffa494acb54317.png

当 mspan 空间不够时,mspan 会向 mcentral 申请更多的 mspans。

mcentral

mcentral 中心缓存是持有 mspans,包含两个链表:

  1. empty: 包含空闲对象的链表
  2. nonempty:不包含空闲对象的链表

访问中心缓存需要锁

https://sourcegraph.com/github.com/golang/go@619072be4138e3fc092a9b77d57a9abc5333a4ab/-/blob/src/runtime/mcentral.go

 1type mcentral struct {
 2    lock      mutex
 3    spanclass spanClass
 4
 5    // For !go115NewMCentralImpl.
 6    nonempty mSpanList // list of spans with a free object, ie a nonempty free list
 7    empty    mSpanList // list of spans with no free objects (or cached in an mcache)
 8
 9    ...
10}

2686c14abf11173cb66249ce6a693f46.png

当 mcentral 空间不足时,mcentral 会向 mheap 申请更多的页用来建立新的 mspan

mheap

mheap 是 go 中管理 heap 的全局唯一结构,它管理着虚拟内存空间

https://sourcegraph.com/github.com/golang/go@619072be4138e3fc092a9b77d57a9abc5333a4ab/-/blob/src/runtime/mheap.go#L73:2

 1type mheap struct {
 2    // lock must only be acquired on the system stack, otherwise a g
 3    // could self-deadlock if its stack grows with the lock held.
 4    lock      mutex
 5    pages     pageAlloc // page allocation data structure
 6
 7    // In general, this is a two-level mapping consisting of an L1
 8    // map and possibly many L2 maps. This saves space when there
 9    // are a huge number of arena frames. However, on many
10    // platforms (even 64-bit), arenaL1Bits is 0, making this
11    // effectively a single-level map. In this case, arenas[0]
12    // will never be nil.
13    arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
14
15    // central free lists for small size classes.
16    // the padding makes sure that the mcentrals are
17    // spaced CacheLinePadSize bytes apart, so that each mcentral.lock
18    // gets its own cache line.
19    // central is indexed by spanClass.
20    central [numSpanClasses]struct {
21        mcentral mcentral
22        pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
23    }
24
25    ...
26}

mheap 中有两个重要的字段:

  1. central: 全局中心缓存,共 67 + 67 个
  2. arenas: 二维矩阵,heap 的内存区域,每个 areana 管理着一系列 8k pages

603b254b3d3cb5376d37c79fb28b065c.png

参考链接

  1. Go 内存分配器
  2. A visual guide to Go Memory Allocator from scratch (Golang)
  3. 深入Linux内核架构
  4. 虚拟内存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值