好的,我将整合之前的回答并详细说明 mspan
、mcache
、mcentral
、mheap
和 heapArena
的使用场景,以及它们与 Thread Cache、Central Cache 和 Heap 的关系。
内存分配器的分层结构
Go 的内存分配器是一个分层的系统,包含以下主要组件:
- Thread Cache(线程缓存)
- Central Cache(中央缓存)
- Heap(堆)
关键组件及其关系
- mspan
- mcache
- mcentral
- mheap
- heapArena
关系图示
Thread Cache (mcache)
|
Central Cache (mcentral)
|
Heap (mheap)
|
heapArena
组件详解及使用场景
1. Thread Cache(线程缓存)/ mcache
mcache
是每个 P(Processor)私有的缓存,用于分配小对象(小于 32 KB)。它包含多个 mspan
,每个 mspan
用于存储相同大小类别的对象。
使用场景:
- 适用于频繁分配和释放的小对象,减少线程之间的竞争,提高分配效率。
例子:
func allocateSmallObject(size int) {
// 分配一个小对象(< 32 KB)
mem := make([]byte, size)
// 使用内存防止被优化掉
mem[0] = 1
fmt.Println("Allocated small object")
}
2. Central Cache(中央缓存)/ mcentral
mcentral
管理每个大小类别的 mspan
,负责从 mheap
获取新的 mspan
,并将用完的 mspan
归还给 mheap
。它充当 mcache
和 mheap
之间的桥梁。
使用场景:
- 当
mcache
中的内存不足时,从mcentral
获取新的mspan
。 - 当
mcache
中有多余内存时,将其归还给mcentral
。
例子:
func allocateFromCentralCache(sizeClass int) {
// 从中央缓存分配内存
span := mcentral.alloc(sizeClass)
// 使用 span 分配对象
obj := span.allocateObject()
fmt.Println("Allocated object from central cache")
}
3. Heap(堆)/ mheap
mheap
是全局的堆内存管理器,负责从操作系统获取内存并管理 mspan
的分配和回收。它提供大块内存(页),并将这些内存分成 mspan
供 mcentral
使用。
使用场景:
- 适用于大对象分配(>= 32 KB)。
- 当
mcentral
请求新的mspan
时,从mheap
获取内存。
例子:
func allocateLargeObject(size int) {
// 分配一个大对象(>= 32 KB)
mem := make([]byte, size)
// 使用内存防止被优化掉
mem[0] = 1
fmt.Println("Allocated large object")
}
4. mspan
mspan
是一组连续的内存页,用于分配对象。mspan
按大小类别(class)划分,每个类别包含大小相似的对象。
使用场景:
- 管理相同大小的对象,以提高分配和释放效率。
- 由
mcache
和mcentral
使用,用于对象分配。
例子:
func allocateFromSpan(span *mspan, size int) {
// 从 span 分配对象
obj := span.allocate(size)
fmt.Println("Allocated object from span")
}
5. heapArena
heapArena
是 mheap
从操作系统获取的大块内存区域,每个 heapArena
包含 64 MB 的内存。用于管理和分配内存页。
使用场景:
mheap
从操作系统获取内存时,将其分割成heapArena
。- 提供基础内存给
mheap
管理。
例子:
func allocateFromHeapArena(size int) {
// 从 heapArena 分配内存
arena := newHeapArena(size)
fmt.Println("Allocated memory from heap arena")
}
内存分配流程
-
小对象分配(<32 KB)
- 当 goroutine 请求分配一个小对象时,
mcache
首先检查是否有足够的内存可用。 - 如果
mcache
有足够的内存,直接分配并返回对象。 - 如果
mcache
没有足够的内存,会向mcentral
请求一个新的mspan
。 - 如果
mcentral
没有可用的mspan
,会向mheap
请求一个新的mspan
。
- 当 goroutine 请求分配一个小对象时,
-
大对象分配(>=32 KB)
- 当 goroutine 请求分配一个大对象时,直接向
mheap
请求内存。 mheap
分配相应大小的内存页并返回。
- 当 goroutine 请求分配一个大对象时,直接向
总结
Go 的内存分配器通过 mcache
、mcentral
和 mheap
分层管理内存,提高了内存分配的效率和并发性能。mspan
和 heapArena
提供了基础的内存管理结构。虽然这种分层设计可能导致内存碎片问题,但其高效的分配和释放机制非常适合 Go 语言的并发编程模型。
下面是整合的代码示例,展示了各个组件的使用:
package main
import (
"fmt"
"sync"
)
func allocateSmallObject(size int) {
// 分配一个小对象(< 32 KB)
mem := make([]byte, size)
// 使用内存防止被优化掉
mem[0] = 1
fmt.Println("Allocated small object")
}
func allocateFromCentralCache(sizeClass int) {
// 模拟从中央缓存分配内存
fmt.Println("Allocated object from central cache of size class", sizeClass)
}
func allocateLargeObject(size int) {
// 分配一个大对象(>= 32 KB)
mem := make([]byte, size)
// 使用内存防止被优化掉
mem[0] = 1
fmt.Println("Allocated large object")
}
func allocateFromSpan(spanSize, objSize int) {
// 模拟从 span 分配对象
fmt.Printf("Allocated object of size %d from span of size %d\n", objSize, spanSize)
}
func allocateFromHeapArena(size int) {
// 模拟从 heapArena 分配内存
fmt.Println("Allocated memory from heap arena of size", size)
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
// 小对象分配
go func() {
defer wg.Done()
allocateSmallObject(16 * 1024) // 16 KB
}()
// 从中央缓存分配对象
go func() {
defer wg.Done()
allocateFromCentralCache(1) // Size class 1
}()
// 大对象分配
go func() {
defer wg.Done()
allocateLargeObject(64 * 1024) // 64 KB
}()
wg.Wait()
fmt.Println("All allocations finished")
}
在这个例子中,我们展示了如何分配小对象、大对象以及从中央缓存和 mspan
、heapArena
中分配内存,体现了 Go 内存分配器的分层结构和各个组件的作用。