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,如闲置时间超过阈值,则释放其关联的物理内存。