Go 内存分配机制

Go语言的内存管理器

页分配器(向操作系统申请内存)、

对象分配器(为用户程序分配内存)、

Gc(回收用户程序所分配的内存)、

拾荒器(向操作系统归还已申请的内存)。

Go内存分配机制

当程序开始时,页分配器向操作系统申请空间,将空间驻留在运行中。

用户需要分配内存时,运行的对象分配器分配内存使用。

go内存分配

内置运行时的编程语言通常会抛弃传统的内存分配方式,改由自主管理。这样可以完成类似预分配、内存池等操作,以避开系统调用带来的性能问题。当然,有一个重要原因是为了更好地配合垃圾回收。

在 Go 语言的运行时系统中,"span" 和 "object" 是两个与内存分配和管理相关的重要概念。

Span(跨度): Span 是 Go 运行时系统中用来管理堆上内存的基本单位。它表示了一块连续的内存区域,通常是大小固定的。Span 的大小可以是 8KB、16KB、32KB 等。
运行时系统会预先从操作系统请求大块的内存,然后将这些内存区域划分为一系列的 Spans,用于分配对象。
Spans 是为了更高效地分配和回收小对象而设计的,它们在堆上形成了一种管理结构,可以避免过度碎片化。

Object(对象): Object 是指在堆上被分配的内存块,也就是运行时系统中表示各种数据结构的实例。每个 Object 包含对象的实际数据和一些元数据,用于管理对象的信息,比如类型信息和内存回收信息。对象的分配和释放都是由运行时系统自动管理的,不需要开发者手动参与。

综合起来,Go 运行时系统通过使用 Spans 来管理堆上的内存分配,将大块内存划分为连续的 Spans,并将其中的内存块用于分配对象。对象是在这些 Spans 中分配的,而运行时系统会跟踪和管理这些对象的生命周期,以及在不再被引用时进行垃圾回收。这种内存管理的方式让开发者从手动内存管理中解放出来,同时提供了高效的内存分配和回收机制。

type mspan struct {
	next *mspan     // Next span in list,如果没有则为nil
	prev *mspan     // list中的前一个span,如果没有则为nil
	list *mSpanList //进行调试。待办事项:删除。

	startAddr uintptr // span的第一个字节的地址,也就是s.base()
	npages    uintptr // 在跨度内的页数

	manualFreeList gclinkptr // mSpanManual中的空闲对象列表闲对象列表
}

a

 概述

1.每次从操作系统申请一大块内存(比如1MB),以减少系统调用。
2.将申请到的大块内存按照特定大小预先切分成小块,构成链表。
3.为对象分配内存时,只须从大小合适的链表提取一个小块即可。 内存分配
4.回收对象内存时,将该小块内存重新归还到原链表,以便复用。 内存回收
5.如闲置内存过多,则尝试归还部分内存给操作系统,降低整体开销。内存释放

内存分配器只管理内存块,并不关心对象状态。且它不会主动回收内存,垃圾回收器在完成清理操作后,触发内存分配器的回收操作。

type mspan struct{next *mspan 
// 双向链表prev *mspanstart pageID 
// 起始序号 = (address>> _PageShift)npages uintptr 
// 页数freelist gclinkptr 
// 待分配的object链表
}

分配器由三种组件组成。cache:每个运行期工作线程都会绑定一个cache,用于无锁object分配。central:为所有cache提供切分好的后备span资源。heap:管理闲置span,需要时向操作系统申请新内存。

type mheap struct{free [_MaxMHeapList]mspan // 页数在127以内的闲置span链表数组freelarge mspan // 页数大于127(>=1MB) 的大span链表// 每个central对应一种sizeclasscentral[_NumSizeClasses]struct{mcentral mcentral}}

type mcentral struct{sizeclass int32 // 规格nonempty mspan // 链表:尚有空闲object的spanempty mspan // 链表:没有空闲object,或已被cache取走的span}

type mcache struct{alloc[_NumSizeClasses]*mspan // 以sizeclass为索引管理多个用于分配的span}

分配流程:1.计算待分配对象对应的规格(size class)。2.从cache.alloc数组找到规格相同的span。3.从span.freelist链表提取可用object。4.如span.freelist为空,从central获取新span。5.如central.nonempty为空,从heap.free/freelarge获取,并切分成object链表。6.如heap没有大小合适的闲置span,向操作系统申请新内存块。

释放流程:1.将标记为可回收的object交还给所属span.freelist。2.该span被放回central,可供任意cache重新获取使用。3.如span已收回全部object,则将其交还给heap,以便重新切分复用。4.定期扫描heap里长时间闲置的span,释放其占用的内存。

16.3 分配

Go编译器支持逃逸分析(escape analysis),它会在编译期通过构建调用图来分析局部变量是否会被外部引用,从而决定是否可直接分配在栈上。

newobject具体是如何为对象分配内存的

整理一下这段代码的基本思路:大对象直接从heap获取span。小对象从cache.alloc[sizeclass].freelist获取object。微小对象组合使用cache.tiny object。

16.4 回收

遍历span,将收集到的不可达object合并到freelist链表。如该span已收回全部object,那么就将这块完全自由的内存还给heap,以便后续复用。

无论是向操作系统申请内存,还是清理回收内存,只要往heap里放span,都会尝试合并左右相邻的闲置span,以构成更大的自由块。

回收操作至此结束。这些被收回的span并不会被释放,而是等待复用。

16.5 释放

在运行时入口函数main.main里,会专门启动一个监控任务sysmon,它每隔一段时间就会检查heap里的闲置内存块。

遍历free、freelarge里的所有span,如闲置时间超过阈值,则释放其关联的物理内存。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值