golang 源码剖析(3): 内存分配

本文深入探讨了Golang的内存管理机制,包括基本策略、内存块、管理组件、分配流程、回收机制以及初始化过程。文章详细解释了如何通过预先切割内存、使用span和object进行高效分配,以及在不同层级(cache、central、heap)的管理。此外,还介绍了内存回收的步骤,如何在对象不再使用时归还内存给系统,以及内存初始化和释放的细节。
摘要由CSDN通过智能技术生成

基本概念

## 基本策略:

  1. 先从操作系统申请一块大内存,以减少系统调用
  2. 将申请到的内存按照特定大小预先切成小块,构成一个链表
  3. 为对象分配内存时,只需从链表中取出一个大小合适的块使用就好
  4. 回收对象内存是,只需将对象放回原链表,以便服用
  5. 闲置过多时,会将部分内存归还系统,降低整体开销

内存块

分配器将其管理的内存块分成两种:

  • span: 有多个地址连续的页(page)组成的大块内存
  • object: 将span按特定大小分成多个小块, 每个小块可存储一个对象

用途上来说,span面向的是内部管理,object面向的是对象的分配.
分配器按页数大小来区分不同大小的span。比如,以页数为单位将span存放到管理数组中,需要的时候就可以以页数为索引进行查找.
runtime/sizeclasses.go中写了预定义的大小

const (
	_MaxSmallSize   = 32768 //最大小对象size, 32*1024byte
	smallSizeDiv    = 8
	smallSizeMax    = 1024
	largeSizeDiv    = 128
	_NumSizeClasses = 67 //size类别数量
	_PageShift      = 13
	_PageSize = 1 << _PageShift // 8kb
)

这里展示了mspan的结构,
spanclass指定了是那种size-class
freeindex则是用于发现下一个free object用的
allocBits 是gc bitmap
sweepgen 用来标志gc清理状态
runtime/mheap.go

type mspan struct {
	next *mspan     // next span in list, or nil if none
	prev *mspan     // previous span in list, or nil if none

	startAddr uintptr // address of first byte of span aka s.base()
	npages    uintptr // number of pages in span
	manualFreeList gclinkptr // list of free objects in mSpanManual spans

	// freeindex is the slot index between 0 and nelems at which to begin scanning
	// for the next free object in this span.
	// Each allocation scans allocBits starting at freeindex until it encounters a 0
	// indicating a free object. freeindex is then adjusted so that subsequent scans begin
	// just past the newly discovered free object.
	//
	// If freeindex == nelem, this span has no free objects.
	//
	// allocBits is a bitmap of objects in this span.
	// If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
	// then object n is free;
	// otherwise, object n is allocated. Bits starting at nelem are
	// undefined and should never be referenced.
	//
	// Object n starts at address n*elemsize + (start << pageShift).
	freeindex uintptr
	allocBits  *gcBits
	spanclass   spanClass  // size class and noscan (uint8)
	elemsize    uintptr    // computed from sizeclass or from npages
	// sweep generation:
	// if sweepgen == h->sweepgen - 2, the span needs sweeping
	// if sweepgen == h->sweepgen - 1, the span is currently being swept
	// if sweepgen == h->sweepgen, the span is swept and ready to use
	// if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
	// if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
	// h->sweepgen is incremented by 2 after every GC

	sweepgen    uint32
}

size-class的分配通过runtime/mksizeclasses.go生成:
size根据align递增,同时对齐, 这里计算了最小浪费和最大浪费

	align := 8
	for size := align; size <= maxSmallSize; size += align {
		if powerOfTwo(size) { // bump alignment once in a while
			if size >= 2048 {
				align = 256
			} else if size >= 128 {
				align = size / 8
			} else if size >= 16 {
				align = 16 // required for x86 SSE instructions, if we want to use them
			}
		}

	spanSize := c.npages * pageSize
	objects := spanSize / c.size
	tailWaste := spanSize - c.size*(spanSize/c.size) //这里的浪费是对齐引起的浪费
	maxWaste := float64((c.size-prevSize-1)*objects+tailWaste) / float64(spanSize) // 这里是对其浪费再加上装入前一个size-class的对象引起的浪费

管理组件

正如上篇文章讲的,这里用到的是cache,central,heap三个组件来管理内存
runtime/mheap.go

type mheap struct {
	// lock must only be acquired on the system stack, otherwise a g
	// could self-deadlock if its stack grows with the lock held.
	lock      mutex
	free      mTreap // free spans
	// central free lists for small size classes.
	// the padding makes sure that the mcentrals are
	// spaced CacheLinePadSize bytes apart, so that each mcentral.lock
	// gets its own cache line.
	// central is indexed by spanClass.
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}
}

runtime/mcentral.go

type mcentral struct {
	lock      mutex
	spanclass spanClass
	nonempty  mSpanList // list of spans with a free object, ie a nonempty free list
	empty     mSpanList // list of spans with no free objects (or cached in an mcache)

	// nmalloc is the cumulative count of objects allocated from
	// this mcentral, assuming all spans in mcaches are
	// fully-allocated. Written atomically, read under STW.
	nmalloc uint64
}

runtime/mcache.go

type mcache struct {
    // tiny is a heap pointer. Since mcache is in non-GC'd memory,
	// we handle it by clearing it in releaseAll during mark
	// termination.
	tiny             uintptr
	tinyoffset       uintptr
	local_tinyallocs uintptr // number of tiny allocs not counted in other stats
	alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
}

分配流程

numSpanClasses = _NumSizeClasses << 1

  1. 计算待分配对象的规格(size-class)
  2. 先找到对应sizeclass的span,根据span.freeindex尝试nextFreeFast(将下一个空闲对象地址放在这里,可快速获取),从span.allocCache中查找是否有空闲的object,如果有则直接返回
  3. 从cache.alloc数组中知道相同size-class的span,如果存在则返回
  4. 如果不存在,会在之后启动gc,同时通过mheap_.central[spc].mcent
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值