表 7-3 平台与页堆大小的关系
本节将介绍页堆的初始化、内存分配以及内存管理单元分配的过程,这些过程能够帮助我们理解全局变量页堆与其他组件的关系以及它管理内存的方式 。
初始化堆区的初始化会使用 runtime.mheap.init 方法,我们能看到该方法初始化了非常多的结构体和字段,不过其中初始化的两类变量比较重要:
- spanalloc、cachealloc 以及 arenaHintAlloc 等 runtime.fixalloc 类型的空闲链表分配器;
- central 切片中 runtime.mcentral 类型的中心缓存;
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.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys) h.spanalloc.zero = false for i := range h.central {h.central[i].mcentral.init(spanClass(i)) } h.pages.init(&h.lock, &memstats.gc_sys)}
堆中初始化的多个空闲链表分配器与我们在设计原理一节中提到的分配器没有太多区别,当我们调用 runtime.fixalloc.init 初始化分配器时,需要传入带初始化的结构体大小等信息,这会帮助分配器分割待分配的内存,该分配器提供了以下两个用于分配和释放内存的方法:- runtime.fixalloc.alloc — 获取下一个空闲的内存空间;
- runtime.fixalloc.free — 释放指针指向的内存空间;
内存管理单元runtime.mheap 是内存分配器中的核心组件,运行时会通过它的 runtime.mheap.alloc 方法在系统栈中获取新的 runtime.mspan:
func (h *mheap) alloc(npages uintptr, spanclass spanClass, needzero bool) *mspan { var s *mspan systemstack(func() {if h.sweepdone == 0 {h.reclaim(npages)}s = h.allocSpan(npages, false, spanclass, &memstats.heap_inuse) }) ... return s}
为了阻止内存的大量占用和堆的增长,我们在分配对应页数的内存前需要先调用 runtime.mheap.reclaim 方法回收一部分内存,接下来我们将通过 runtime.mheap.allocSpan 分配新的内存管理单元,我们会将该方法的执行过程拆分成两个部分:- 从堆上分配新的内存页和内存管理单元 runtime.mspan;
- 初始化内存管理单元并将其加入 runtime.mheap 持有内存单元列表;
func (h *mheap) allocSpan(npages uintptr, manual bool, spanclass spanClass, sysStat *uint64) (s *mspan) { gp := getg() base, scav := uintptr(0), uintptr(0) pp := gp.m.p.ptr() if pp != nil && npages < pageCachePages/4 {c := &pp.pcachebase, scav = c.alloc(npages)if base != 0 {s = h.tryAllocMSpan()if s != nil && gcBlackenEnabled == 0 && (manual || spanclass.sizeclass() != 0) {goto HaveSpan}} } if base == 0 {base, scav = h.pages.alloc(npages)if base == 0 {h.grow(npages)base, scav = h.pages.alloc(npages)if base == 0 {throw("grew heap, but no adequate free space found")}} } if s == nil {s = h.allocMSpanLocked() } ...}
上述方法会通过处理器的页缓存 runtime.pageCache 或者全局的页分配器runtime.pageAlloc 两种途径从堆中申请内存:- 如果申请的内存比较小,获取申请内存的处理器并尝试调用 runtime.pageCache.alloc 获取内存区域的基地址和大小;
- 如果申请的内存比较大或者线程的页缓存中内存不足,会通过 runtime.pageAlloc.alloc在页堆上申请内存;
- 如果发现页堆上的内存不足,会尝试通过 runtime.mheap.grow 进行扩容并重新调用 runtime.pageAlloc.alloc 申请内存;
- 如果申请到内存,意味着扩容成功;
- 如果没有申请到内存,意味着扩容失败,宿主机可能不存在空闲内存,运行时会直接中止当前程序;
func (h *mheap) alloc(npages uintptr, spanclass spanClass, needzero bool) *mspan { ...HaveSpan: s.init(base, npages) ... s.freeindex = 0 s.allocCache = ^uint64(0) s.gcmarkBits = newMarkBits(s.nelems) s.allocBits = newAllocBits(s.nelems) h.setSpans(s.base(), npages, s) return s}
在上述代码中,我们通过调用 runtime.mspan.init 方法以及设置参数初始化刚刚分配的 runtime.mspan 结构并通过 runtime.mheaps.setSpans 方法建立页堆与内存单元的联系 。
推荐阅读
- 茶艺享受的是时间,茶艺根据不同的原则和方法的分类
- 茶油的营养价值,长期吃茶油的坏处
- 茶具的选购之配套用具,茶具的奇思妙想
- 科学管理Linux系统中的组与组成员
- 大红袍的产地,名茶大红袍的产地是哪
- svchost占用内存过高是怎么回事
- 基于MEC的边缘CDN业务调度方案及测试分析
- 茶香面包的做法,红茶面包棒的做法
- JavaScript的Array.flat函数深入探讨
- 买电脑、DIY电脑,你必须了解的避坑技能