golang 内存管理

内存管理

内存分配器

线性分配(Bump Allocator)

指针指向空闲内存地址,修改更新指针来分配下一个内存地址。

  1. 复杂度低,执行效率高
  2. 内存被回收后无法重用
    1. 需要垃圾回收机制,标记/复制/分代回收算法整理内存碎片
    2. c和c++对外暴露指针,无法使用。

bump-allocator-reclaim-memory

空闲链表分配(Free-List Allocator)

用链表结构管理空闲内存块,通过修改更新链表连接情况分配内存地址。

  1. 方便可进行内存回收重用
  2. 分配内存检索是O(n)
    1. 首次适应(First-Fit),链表头遍历选择第一个大于申请内存的内存块
    2. 循环首次适应(Next-Fit),从上一个遍历结束地址,选择第一个大于申请内存的内存块
    3. 最优适应(Best-Fit),链表头遍历,选择最合适的内存块
    4. 隔离适应(Segregated-Fit),内存分割多个连表,每个连表内存块大小相同,申请内存先找合适连表,在找合适内存块

free-list-allocator

runtime/mfixalloc.go

// 空闲链表分配器   最优适应
type fixalloc struct {
	size   uintptr // 对象大小
	first  func(arg, p unsafe.Pointer) // called first time p is returned
	arg    unsafe.Pointer
	list   *mlink
	chunk  uintptr // use uintptr instead of unsafe.Pointer to avoid write barriers
	nchunk uint32
	inuse  uintptr // in-use bytes now
	stat   *sysMemStat
	zero   bool // zero allocations
}

线程缓存分配 (Thread-Caching Malloc,TCMalloc)

go语言内存分配器,借鉴TCMalloc,多级缓冲根据对象大小采取不同分配策略

对象大小
类别大小
微对象(0, 16B)
小对象[16B, 32KB]
大对象(32KB, +∞)

runtime/sizeclasses.go

// classid       每span结构都有一个classid
// bytes/obj     class对象的字节数
// bytes/span    每span占用堆的字节数(pagenum*pagesize)
// object        每span可分配的对象数(bytes/spans)/(bytes/obj)
// waste bytes   每span产生的内存碎片(bytes/spans)%(bytes/obj)
// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         24        8192      341           8     29.24%
//     4         32        8192      256           0     21.88%
//     5         48        8192      170          32     31.52%
//     6         64        8192      128           0     23.44%
//     7         80        8192      102          32     19.07%
//     8         96        8192       85          32     15.95%
//     9        112        8192       73          16     13.56%
//    10        128        8192       64           0     11.72%
//    11        144        8192       56         128     11.82%
//    12        160        8192       51          32      9.73%
//    13        176        8192       46          96      9.59%
//    14        192        8192       42         128      9.25%
//    15        208        8192       39          80      8.12%
//    16        224        8192       36         128      8.15%
//    17        240        8192       34          32      6.62%
//    18        256        8192       32           0      5.86%
//    19        288        8192       28         128     12.16%
//    20        320        8192       25         192     11.80%
//    21        352        8192       23          96      9.88%
//    22        384        8192       21         128      9.51%
//    23        416        8192       19         288     10.71%
//    24        448        8192       18         128      8.37%
//    25        480        8192       17          32      6.82%
//    26        512        8192       16           0      6.05%
//    27        576        8192       14         128     12.33%
//    28        640        8192       12         512     15.48%
//    29        704        8192       11         448     13.93%
//    30        768        8192       10         512     13.94%
//    31        896        8192        9         128     15.52%
//    32       1024        8192        8           0     12.40%
//    33       1152        8192        7         128     12.41%
//    34       1280        8192        6         512     15.55%
//    35       1408       16384       11         896     14.00%
//    36       1536        8192        5         512     14.00%
//    37       1792       16384        9         256     15.57%
//    38       2048        8192        4           0     12.45%
//    39       2304       16384        7         256     12.46%
//    40       2688        8192        3         128     15.59%
//    41       3072       24576        8           0     12.47%
//    42       3200       16384        5         384      6.22%
//    43       3456       24576        7         384      8.83%
//    44       4096        8192        2           0     15.60%
//    45       4864       24576        5         256     16.65%
//    46       5376       16384        3         256     10.92%
//    47       6144       24576        4           0     12.48%
//    48       6528       32768        5         128      6.23%
//    49       6784       40960        6         256      4.36%
//    50       6912       49152        7         768      3.37%
//    51       8192        8192        1           0     15.61%
//    52       9472       57344        6         512     14.28%
//    53       9728       49152        5         512      3.64%
//    54      10240       40960        4           0      4.99%
//    55      10880       32768        3         128      6.24%
//    56      12288       24576        2           0     11.45%
//    57      13568       40960        3         256      9.99%
//    58      14336       57344        4           0      5.35%
//    59      16384       16384        1           0     12.49%
//    60      18432       73728        4           0     11.11%
//    61      19072       57344        3         128      3.57%
//    62      20480       40960        2           0      6.87%
//    63      21760       65536        3         256      6.25%
//    64      24576       24576        1           0     11.45%
//    65      27264       81920        3         128     10.00%
//    66      28672       57344        2           0      4.91%
//    67      32768       32768        1           0     12.50%

多级缓存
  1. 线程缓存(Thread Cache)
    1. 线程内无竞争无锁
    2. 内存空间有限,负责小对象创建,大对象直接选择页堆分配
  2. 中心缓存(Central Cache)
  3. 页堆(Page Heap)

multi-level-cache

地址空间状态转移

状态

状态解释
None内存没有被保留或者映射,是地址空间的默认状态
Reserved运行时持有该地址空间,但是访问该内存会导致错误
Prepared内存被保留,一般没有对应的物理内存访问该片内存的行为是未定义的可以快速转换到 Ready 状态
Ready可以被安全访问

状态转移函数

名称解释
runtime.sysAlloc会从操作系统中获取一大块可用的内存空间,可能为几百 KB 或者几 MB;
runtime.sysFree会在程序发生内存不足(Out-of Memory,OOM)时调用并无条件地返回内存;
runtime.sysReserve会保留操作系统中的一片内存区域,访问这片内存会触发异常;
runtime.sysUsed通知操作系统应用程序需要使用该内存区域,保证内存区域可以安全访问;
runtime.sysUnused通知操作系统虚拟内存对应的物理内存已经不再需要,可以重用物理内存;
runtime.sysFault将内存区域转换成保留状态,主要用于运行时的调试;
runtime.sysMap保证内存区域可以快速转换至就绪状态;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbqB2h7c-1652891154059)(https://img.draveness.me/2020-02-29-15829868066474-memory-regions-states-and-transitions.png)]

内存对齐

内存地址是所存储大小(子节)的整数倍,以便cpu可以一次将数据从内存中读取出来。

优点

  1. 提高可移植性,编译时将内存进行对齐。消除CPU的差异性,达到代码可移植。
  2. 提高内存的访问效率。32位CPU一次读取4个字节,64位CPU一次读取8个字节,这个长度称为CPU字长,将内存变为CPU字长整数倍,减少CPU碎片化读取,提高访问效率。

缺点

  1. 浪费内存空间,换取时间

结构体

  1. 结构体变量中成员的偏移量必须是成员大小的整数倍
  2. 整个结构体的地址必须是最大字节的整数倍(1/4/8/16…)

内存逃逸

每个函数都有自己的内存区域存放入参,局部变量,返回值等,存放于运行栈中栈帧中,函数运行后自动销毁。
函数结束后,如果还需要这些变量将会导致栈中数据分配到堆中,这种现象叫内存逃逸

逃逸机制

  1. 函数外部没有引用,优先放栈
  2. 函数外部存在引用,必定放堆
  3. 栈放不下,必定放堆

逃逸分析

go build -gcflags=-m main.go
  1. 指针逃逸
package main

func escape1() *int{
  var a int = 1
  return &a
}

func main(){
  escape1()
}

// ./main.go 4:6 moved to heap a
  1. 栈空间不足
package main

func escape2() {
  s :=make([]int,0,100000)
  for index,_:=range s{
    s[index]=index
  }
}

func main(){
  escape2()
}

// ./main.go 4:1 make([]int,1000,1000) escapes to heap
  1. 变量大小不缺定
package main

func escape3() *int{
  number:=10
  s :=make([]int,number)
  for index:=0;index<len(s);index++{
    s[index]=index
  }
}

func main(){
  escape3()
}

//./main.go:18:6: can inline escape3
//./main.go:15:9: inlining call to escape3
//./main.go:15:9: make([]int, number) escapes to heap
//./main.go:20:10: make([]int, number) escapes to heap
  1. 动态类型
package main

func escape4() {
  fmt.println(1111)
}

func main(){
  escape4()
}

//./main.go:33:6: can inline escape4
./main.go:34:13: inlining call to fmt.Println
//./main.go:6:6: can inline main
//./main.go:10:9: inlining call to escape4
//./main.go:10:9: inlining call to fmt.Println
//./main.go:10:9: 1111 escapes to heap
//./main.go:10:9: []interface {}{...} does not escape
//./main.go:34:14: 1111 escapes to heap
//./main.go:34:13: []interface {}{...} does not escape
//<autogenerated>:1: .this does not escape
  1. 闭包引用
package main

func escape5() func()int {
  var i int =1
  return func() int {
    i++
    return
  }
}

func main(){
  escape5()
}
//./main.go:36:9: can inline escape5.func1
//./main.go:4:6: can inline main
//./main.go:35:6: moved to heap: i
//./main.go:36:9: func literal escapes to heap

总结

  1. 栈上分配比堆内存分配效率更高,且不需要gc
  2. 逃逸分析目的是决定内存地址是栈还是堆,来分析gc存在性能瓶颈,编译时就可以知道
  3. 只要是指针变量都会在堆上分配,对于小变量使用值传递效率更高

go 内存管理

虚拟内存布局

go1.10版本以及以前使用连续内存

heap-before-go-1-10

  • spans

    • 存储内存管理单元mspan指针,每个指针对于1orN个page
    • 512MB = 512GB/8KB(页大小)*8B(指针大小)
  • bitmap

    • 每个字节都会表示 arena 区域中的 32 字节是否空闲

    • 主要用于gc

    • 16G = 512GB/32B*1B

  • arena

    • 真正的堆区
    • 512GB,page=8KB

g1.11版本以及之后引入二维稀疏内存

  • [l1][l2]heapArena
    • Linux x86-64架构 l1=1 l2=4194304 32MB = 4194304*8B(指针大小) ,每个heapArena可以管理64MB内存
    • 4M*64MB = 256TB内存

heap-after-go-1-11

// runtime.heapArena
type heapArena struct {
	bitmap       [heapArenaBitmapBytes]byte // 标记内内存是否使用
	spans        [pagesPerArena]*mspan      // 管理器mspan
	pageInUse    [pagesPerArena / 8]uint8
	pageMarks    [pagesPerArena / 8]uint8
	pageSpecials [pagesPerArena / 8]uint8
	checkmarks   *checkmarksMap
	zeroedBase   uintptr                    //内存管理基地址
}

go内存管理设计

  1. 借鉴TCMalloc,采用多级缓冲
    1. mcache 线程内存分配对应结构
    2. mcentral 中心内存分配对应结构
    3. mheap 页堆内存分配对应结构
    4. mspan 内存管理单元结构体,对应class表
  2. 内存分配器采用线性/空闲列表结合
    1. 微对象使用mcache内的线性分配,若无则同小对象
    2. 小对象依次class表分级内存分配,最终使用空闲链表分配
    3. 大对象直接使用mheap内使用空闲链表分配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0nLBZ6R-1652891154061)(https://img.draveness.me/2020-02-29-15829868066479-go-memory-layout.png)]

mspan

  1. 内存管理单元,mcache,mheap均使用该结构记录
  2. 链表结构
  3. 只管理一类class,由class表
// runtime/mheap.go
// go:notinheap
type mspan struct {
	next *mspan     // mspan链表下一个指针
	prev *mspan     // mspan链表上一个指针
	list *mSpanList // mspan链表头尾节点指针
  

  // startAddr npages 可以计算出mspan管理的内存块范围
	startAddr   uintptr        // 起始地址,所管理页的地址 
  freeindex   uintptr        // 扫描页中空闲对象初始索引 
  limit       uintptr        // 数据截止位置 申请大对象内存块使用
  
  allocCache  uint64         // allocBits的补码  用于快速查询空闲
	allocBits  *gcBits         // 内存占用情况,对象位图
	gcmarkBits *gcBits         // gc标记情况,对象位图
	allocCount  uint16         // 已分配的对象数 
  
  nelems      uintptr        // 对象总数 对应class表中的 object
	spanclass   spanClass      // classid 对应class表中的 class
  elemsize    uintptr        // 对象大小  对应class表中的 bytes/obj
  npages      uintptr        // span管理的页数*8KB对应class表中bytes/span
	state       mSpanStateBox  // 内存管理状态
  
	manualFreeList gclinkptr   // 空闲对象列表


	needzero    uint8    //
	sweepgen    uint32   //扫描次数
  
  // divMagic
	divMul      uint16        
	baseMask    uint16     
  divShift    uint8         
	divShift2   uint8    
  
	speciallock mutex         
	specials    *special      
}
mcache
  • 微对象分配器(线性分配器)
  • 小对象分配器,缓冲134类mspan作为线程内部使用
  • 大对象分配器,直接使用mheap,其他分配依次mcache mcentral mheap
// runtime/mcache.go
// go:notinheap
type mcache struct {

  // 微对象分配器
	tiny       uintptr   //tiny对象基地址
	tinyoffset uintptr   //下一个空闲内存所在偏移量
	tinyAllocs uintptr   //tiny对象分配个数

  // 小对象分配器
  // 偶数位指针类型(需要gc扫描)   奇数位非指针类型
	alloc [numSpanClasses]*mspan //68*2 按class分组的mspan列表

	stackcache [_NumStackOrders]stackfreelist // 栈缓存
	flushGen uint32
  nextSample uintptr // trigger heap sample after allocating this many bytes
	scanAlloc  uintptr // bytes of scannable heap allocated
}
//初始化
//运行处理时P初始化时会调用此方法
func allocmcache() *mcache {
	var c *mcache
	systemstack(func() {
		lock(&mheap_.lock)
		c = (*mcache)(mheap_.cachealloc.alloc())
		c.flushGen = mheap_.sweepgen
		unlock(&mheap_.lock)
	})
	for i := range c.alloc {
    // 所有mspan使用占位填充
		c.alloc[i] = &emptymspan
	}
	c.nextSample = nextSample()
	return c
}


//替换
func (c *mcache) refill(spc spanClass) {
	
	s := c.alloc[spc]

  // 其他代码
  ...
	// 从中心缓存申请msapn
	s = mheap_.central[spc].mcentral.cacheSpan()
	if s == nil {
		throw("out of memory")
	}

	if uintptr(s.allocCount) == s.nelems {
		throw("span has no free space")
	}

  // 其他代码
  ...
	c.alloc[spc] = s
}

mcentral

  • 只管理一类class,由class表

  • 两个包含/不包含空闲对象的mspan列表

    • mspan列表多线程共享,使用互斥锁保护
// runtime/mcentral.go
type mcentral struct {
	spanclass spanClass //classid
	partial [2]spanSet // 空闲的span列表
	full    [2]spanSet // 非空闲的span列表
}


type spanSet struct {
	spineLock mutex          // 多线程访问加锁
	spine     unsafe.Pointer // 指向[]span的指针
	spineLen  uintptr        // 长度
	spineCap  uintptr        // 容量
	index headTailIndex      // 前32位时头指针 后32位时尾指针
}
// 初始化
func (c *mcentral) init(spc spanClass) {
	c.spanclass = spc   // 初始化spanclass
	lockInit(&c.partial[0].spineLock, lockRankSpanSetSpine) // 锁
	lockInit(&c.partial[1].spineLock, lockRankSpanSetSpine) // 锁
	lockInit(&c.full[0].spineLock, lockRankSpanSetSpine)    // 锁
	lockInit(&c.full[1].spineLock, lockRankSpanSetSpine)    // 锁
}


// 管理 mecache 获取mspan
func (c *mcentral) cacheSpan() *mspan {
  // 其他代码
  ...
	var s *mspan

	// 1.尝试从清理过/包含空闲空间的结构中查找
	if s = c.partialSwept(sg).pop(); s != nil {
		goto havespan
	}

	// 2.尝试从未清理过/包含空闲空间的结构中查找
	for ; spanBudget >= 0; spanBudget-- {
		s = c.partialUnswept(sg).pop()
		if s == nil {
			break
		}
		if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
      //清理
			s.sweep(true)
			goto havespan
		}
	}

  // 3.尝试从未被清理过/不包含空闲空间的结构中查找
	for ; spanBudget >= 0; spanBudget-- {
		s = c.fullUnswept(sg).pop()
		if s == nil {
			break
		}
		if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
			// 清理
			s.sweep(true)
			// 检查是否有空闲
			freeIndex := s.nextFreeIndex()
			if freeIndex != s.nelems {
				s.freeindex = freeIndex
				goto havespan
			}
      //
			c.fullSwept(sg).push(s)
		}
	}
	// 其他代码
  ...
	//4. mcentral向mheap申请扩容
	s = c.grow()
	if s == nil {
		return nil
	}

havespan:
	// 其他代码
  ...
	n := int(s.nelems) - int(s.allocCount)
	if n == 0 || s.freeindex == s.nelems || uintptr(s.allocCount) == s.nelems {
		throw("span has no free objects")
	}
	freeByteBase := s.freeindex &^ (64 - 1)
	whichByte := freeByteBase / 8
	s.refillAllocCache(whichByte)
	s.allocCache >>= s.freeindex % 64

	return s
}


// 扩容
func (c *mcentral) grow() *mspan {
  
	npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])
	size := uintptr(class_to_size[c.spanclass.sizeclass()])

	s := mheap_.alloc(npages, c.spanclass, true)
	if s == nil {
		return nil
	}

	// Use division by multiplication and shifts to quickly compute:
	// n := (npages << _PageShift) / size
	n := (npages << _PageShift) >> s.divShift * uintptr(s.divMul) >> s.divShift2
	s.limit = s.base() + size*n
	heapBitsForAddr(s.base()).initSpan(s) // 清理堆上位图
	return s
}


mheap

//go:notinheap
type mheap struct {
	lock      mutex     //多线程锁
	pages     pageAlloc // 页分配器
	allspans []*mspan // 所有mspan 由锁保护
  // heapArena二维希数内存
	arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
   //管理68跨度的mcentral
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}
	
  //其他字段
  ...
}

// runtime.heapArena  管理64MB
type heapArena struct {
	bitmap       [heapArenaBitmapBytes]byte // 标记内内存是否使用
	spans        [pagesPerArena]*mspan      // 管理器mspan
	pageInUse    [pagesPerArena / 8]uint8
	pageMarks    [pagesPerArena / 8]uint8
	pageSpecials [pagesPerArena / 8]uint8
	checkmarks   *checkmarksMap
	zeroedBase   uintptr                    //内存管理基地址
}
// 初始化
func (h *mheap) init() {
	lockInit(&h.lock, lockRankMheap)
	lockInit(&h.speciallock, lockRankMheapSpecial)

	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

  // 初始化mcentral
	for i := range h.central {
		h.central[i].mcentral.init(spanClass(i))
	}

	h.pages.init(&h.lock, &memstats.gcMiscSys)
}

// 管理
// 从系统栈获取新的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, spanAllocHeap, spanclass)
	})
  // 其他代码
  ...
	return s
}

//
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
	// Function-global state.
	gp := getg()
	base, scav := uintptr(0), uintptr(0)
  // 其他代码
  ...
	pp := gp.m.p.ptr()
	if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
		c := &pp.pcache

		// 如果处理器的页缓冲为空
		if c.empty() {
			lock(&h.lock)
      // 全局页分配器放置到p的页缓冲
			*c = h.pages.allocToCache()
			unlock(&h.lock)
		}

		// 处理器页缓冲申请内存
		base, scav = c.alloc(npages)
		if base != 0 {
			s = h.tryAllocMSpan()
			if s != nil {
				goto HaveSpan
			}
		}
	}

	lock(&h.lock)

  // 其他代码
  ...

	if base == 0 {
		// 全局分配器分配页
		base, scav = h.pages.alloc(npages)
		if base == 0 {
      // 扩容失败返回 nil
			if !h.grow(npages) {
				unlock(&h.lock)
				return nil
			}
      // 重新获取
			base, scav = h.pages.alloc(npages)
      // 再次失败抛异常
			if base == 0 {
				throw("grew heap, but no adequate free space found")
			}
		}
	}
	if s == nil {
		s = h.allocMSpanLocked()
	}

  // 其他代码
  ...

	unlock(&h.lock)

HaveSpan:
	// At this point, both s != nil and base != 0, and the heap
	// lock is no longer held. Initialize the span.
	s.init(base, npages)
	if h.allocNeedsZero(base, npages) {
		s.needzero = 1
	}
	nbytes := npages * pageSize
	if typ.manual() {
		s.manualFreeList = 0
		s.nelems = 0
		s.limit = s.base() + s.npages*pageSize
		s.state.set(mSpanManual)
	} else {
		// We must set span properties before the span is published anywhere
		// since we're not holding the heap lock.
		s.spanclass = spanclass
		if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
			s.elemsize = nbytes
			s.nelems = 1

			s.divShift = 0
			s.divMul = 0
			s.divShift2 = 0
			s.baseMask = 0
		} else {
			s.elemsize = uintptr(class_to_size[sizeclass])
			s.nelems = nbytes / s.elemsize

			m := &class_to_divmagic[sizeclass]
			s.divShift = m.shift
			s.divMul = m.mul
			s.divShift2 = m.shift2
			s.baseMask = m.baseMask
		}

		// Initialize mark and allocation structures.
		s.freeindex = 0
		s.allocCache = ^uint64(0) // all 1s indicating all free.
		s.gcmarkBits = newMarkBits(s.nelems)
		s.allocBits = newAllocBits(s.nelems)

	// 其他代码
  ...


	h.setSpans(s.base(), npages, s)
  // 其他代码
  ...
	return s
}

// 扩容
func (h *mheap) grow(npage uintptr) bool {
	assertLockHeld(&h.lock)

	// 计算需要的页总内存
	ask := alignUp(npage, pallocChunkPages) * pageSize

	totalGrowth := uintptr(0)
	end := h.curArena.base + ask
	nBase := alignUp(end, physPageSize)
	if nBase > h.curArena.end || /* overflow */ end < h.curArena.base {
    //arena区域不够需要扩容
		av, asize := h.sysAlloc(ask)
		if av == nil {
			print("runtime: out of memory: cannot allocate ", ask, "-byte block (", memstats.heap_sys, " in use)\n")
			return false
		}

		if uintptr(av) == h.curArena.end {
      //老区扩容
			h.curArena.end = uintptr(av) + asize
		} else {
      //新区域 
			if size := h.curArena.end - h.curArena.base; size != 0 {
				h.pages.grow(h.curArena.base, size)
				totalGrowth += size
			}
			// Switch to the new space.
			h.curArena.base = uintptr(av)
			h.curArena.end = uintptr(av) + asize
		}
		atomic.Xadd64(&memstats.heap_released, int64(asize))
		stats := memstats.heapStats.acquire()
		atomic.Xaddint64(&stats.released, int64(asize))
		memstats.heapStats.release()
		nBase = alignUp(h.curArena.base+ask, physPageSize)
	}

	// 更新arena
	v := h.curArena.base
	h.curArena.base = nBase
  // 更新页分配器
	h.pages.grow(v, nBase-v)
	totalGrowth += nBase - v

  // 回收空闲内存页
	if retained := heapRetained(); retained+uint64(totalGrowth) > h.scavengeGoal {
		todo := totalGrowth
		if overage := uintptr(retained + uint64(totalGrowth) - h.scavengeGoal); todo > overage {
			todo = overage
		}
		h.pages.scavenge(todo, false)
	}
	return true
}

内存分配

// new 操作被编译器翻译调用此方法
func newobject(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// 
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
	// 其他代码
  ...
	mp := acquirem()  // 放置M被GC抢占
  // 其他代码
  ...
	mp.mallocing = 1
	shouldhelpgc := false
  // 1. 计算内存的规格
	dataSize := size
  
  // 2. mcache找到合适规格的块内存
	c := getMCache()  // 获取当前mcache
  // 其他代码
  ...
	var span *mspan
	var x unsafe.Pointer
	noscan := typ == nil || typ.ptrdata == 0
	if size <= maxSmallSize {
		if noscan && size < maxTinySize {
      // 2.1微对象分配
      // 1.先使用微型分配器
      // 2.使用mcache/mcentral/heaparean
			off := c.tinyoffset
			// 进行内存对齐
			if size&7 == 0 {
				off = alignUp(off, 8)
			} else if sys.PtrSize == 4 && size == 12 {
				off = alignUp(off, 8)
			} else if size&3 == 0 {
				off = alignUp(off, 4)
			} else if size&1 == 0 {
				off = alignUp(off, 2)
			}
      //分配
			if off+size <= maxTinySize && c.tiny != 0 {
				x = unsafe.Pointer(c.tiny + off)
				c.tinyoffset = off + size
				c.tinyAllocs++
				mp.mallocing = 0
				releasem(mp)
				return x
			}
			//没有则 通过span分配
			span = c.alloc[tinySpanClass]
			v := nextFreeFast(span)
			if v == 0 {
				v, span, shouldhelpgc = c.nextFree(tinySpanClass)
			}
      // 返回新内存
			x = unsafe.Pointer(v)
			(*[2]uint64)(x)[0] = 0
			(*[2]uint64)(x)[1] = 0
			if size < c.tinyoffset || c.tiny == 0 {
				c.tiny = uintptr(x)
				c.tinyoffset = size
			}
			size = maxTinySize
		} else {
      // 2.2小对象分配
      // 使用mcache/mcentral/heaparean
			var sizeclass uint8
			if size <= smallSizeMax-8 {
				sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
			} else {
				sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
			}
			size = uintptr(class_to_size[sizeclass])
			spc := makeSpanClass(sizeclass, noscan)
			span = c.alloc[spc]
			v := nextFreeFast(span) // 检查管理是否有空闲空间
			if v == 0 {
        //重新mcache/mcentral/heaparean 获取管理单元
				v, span, shouldhelpgc = c.nextFree(spc)
			}
			x = unsafe.Pointer(v)
			if needzero && span.needzero != 0 {
				memclrNoHeapPointers(unsafe.Pointer(v), size)
			}
		}
	} else {
    // 2.3大对象分配
    // 直接在对上分配
		shouldhelpgc = true
    // mheap 申请一个classid = 0 的管理单元
		span = c.allocLarge(size, needzero, noscan)
		span.freeindex = 1
		span.allocCount = 1
		x = unsafe.Pointer(span.base())
		size = span.elemsize
	}

	// 其他代码
  ...
  
	// 内存屏障
	publicationBarrier()

	// 其他代码
  ...
	mp.mallocing = 0
	releasem(mp)
	// 其他代码
  ...
  if shouldhelpgc {
		if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
			gcStart(t)
		}
	}

	return x
}


img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mars'Ares

请我喝杯咖啡吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值