上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
2.6.1 固定分配器
前文所提内存,多指用户对象存储。
但编译后的程序,实际由用户和运行时两部分代码共同组成。运行时相关操作,同样需要分配内存。只是这些系统对象不在保留区域分配,且采取不同生命周期和复用策略。为此,专门设计了 FixAlloc 固定分配器。
从堆数据结构中,可以看到几个使用固定分配器的字段。
// mheap.go type mheap struct { spanalloc fixalloc // allocator for span* cachealloc fixalloc // allocator for mcache* specialfinalizeralloc fixalloc // allocator for specialfinalizer* specialprofilealloc fixalloc // allocator for specialprofile* specialReachableAlloc fixalloc // allocator for specialReachable arenaHintAlloc fixalloc // allocator for arenaHints }
这其中,我们最为熟悉的是管理内存块的 mspan 对象。不同于 mheap 唯一实例,这几个字段各自持有独立固定分配器。在堆初始化方法里,分别被设定不同的分配属性。
// Initialize the heap. func (h *mheap) init() { h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys) h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys) h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys) h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys) h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys) h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys) }
初始化
初始化时,除内存单元大小外,还可指定一关联函数(first)和执行参数(arg)。
// mfixalloc.go // FixAlloc is a simple free-list allocator for fixed size objects. // Malloc uses a FixAlloc wrapped around sysAlloc to manage its // mcache and mspan objects. type fixalloc struct { size uintptr first func(arg, p unsafe.Pointer) // called first time p is returned arg unsafe.Pointer ... stat *sysMemStat zero bool // zero allocations }
// Initialize f to allocate objects of the given size, // using the allocator to obtain chunks of memory. func (f *fixalloc) init(size uintptr, first func(arg, p unsafe.Pointer), arg unsafe.Pointer, stat *sysMemStat) { f.size = size f.first = first f.arg = arg ... f.stat = stat f.zero = true }
分配
从结构上看,固定分配器采用早期内存分配器设计思路。
以链表(list)管理回收可复用内存单元,另持一块待分割内存块(chunk)作为后备资源。
type fixalloc struct { list *mlink chunk uintptr nchunk uint32 // bytes remaining in current chunk nalloc uint32 // size of new chunks in bytes inuse uintptr // in-use bytes now }
分配时优先从复用链表提取,不足再从后备内存分割。如此看来,后备内存 chunk 类似 mheap 在三级结构中地位。另外,关联函数仅在分割新内存单元时执行,很适合做些记录性工作。
func (f *fixalloc) alloc() unsafe.Pointer { // 尝试从复⽤链表提取。 if f.list != nil { v := unsafe.Pointer(f.list) f.list = f.list.next f.inuse += f.size if f.zero { memclrNoHeapPointers(v, f.size) } return v } // 如果后备资源不足,则重新获取 (16 KB)。 if uintptr(f.nchunk) < f.size { f.chunk = uintptr(persistentalloc(uintptr(f.nalloc), 0, f.stat)) f.nchunk = f.nalloc } // 从后备内存块分割。 v := unsafe.Pointer(f.chunk) // 如有关联函数,则执⾏。 if f.first != nil { f.first(f.arg, v) } // 调整后备内存等属性。 f.chunk = f.chunk + f.size f.nchunk -= uint32(f.size) f.inuse += f.size return v }
// mheap.go // allocMSpanLocked allocates an mspan object. func (h *mheap) allocMSpanLocked() *mspan { (*mspan)(h.spanalloc.alloc()) }
回收
回收操作仅将内存单元放回复用链表,并没有与释放相关的行为。
使用 FixAlloc 的几种管理对象,其数量不会太多,不释放物理内存影响不大。
func (f *fixalloc) free(p unsafe.Pointer) { f.inuse -= f.size v := (*mlink)(p) v.next = f.list f.list = v }
手工调用,非 GC 回收。
// mheap.go func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) { s.state.set(mSpanDead) h.freeMSpanLocked(s) } func (h *mheap) freeMSpanLocked(s *mspan) { h.spanalloc.free(unsafe.Pointer(s)) }
后备
实际上,后备内存不仅仅为固定分配器服务,还频繁出现在运行时其他部件中。
// malloc.go type persistentAlloc struct { base *notInHeap off uintptr }
提示:notInHeap 是空结构,包含一个计算偏移地址的方法。
// malloc.go type notInHeap struct{} func (p *notInHeap) add(bytes uintptr) *notInHeap { return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes)) }
基于性能考虑,线程(P)拥有独立后备内存。另以全局变量作为补充。
// runtime2.go type p struct { palloc persistentAlloc // per-P to avoid mutex }
// malloc.go var globalAlloc struct { mutex persistentAlloc }
优先从当前线程内无锁提取。如果不足,再向操作系统申请。
向操作系统申请内存时,直接使用 sysAlloc,避开内存分配器。
// malloc.go // Wrapper around sysAlloc that can allocate small chunks. // There is no associated free operation. func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer { var p *notInHeap p = persistentalloc1(size, align, sysStat) return unsafe.Pointer(p) }
// persistentChunkSize is the number of bytes we allocate when we grow // a persistentAlloc. const persistentChunkSize = 256 << 10 func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap { const ( maxBlock = 64 << 10 ) // 超出 64 KB,直接向操作系统申请。 if size >= maxBlock { return (*notInHeap)(sysAlloc(size, sysStat)) } // 确定后备内存位置,本地或全局。 var persistent *persistentAlloc if mp != nil && mp.p != 0 { persistent = &mp.p.ptr().palloc } else { persistent = &globalAlloc.persistentAlloc } // 如果后备剩余空间不足,则向操作系统申请 256 KB。 if persistent.off+size > persistentChunkSize || persistent.base == nil { persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys)) } // 切分。 p := persistent.base.add(persistent.off) persistent.off += size return p }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论