返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

2.2.2 堆内存管理

发布于 2024-10-12 19:15:55 字数 6951 浏览 0 评论 0 收藏 0

堆内存管理包括:

  • 向操作系统申请内存。
  • 为上层部件分配内存。(alloc, mspan)
  • 管理正在使用和闲置内存。
  • 回收不再使用的内存。(free)

分配、重用和回收管理,由独立单元 pageAlloc 完成。

// mheap.go

type mheap struct {
    pages pageAlloc   // page allocation data structure
}

// Initialize the heap.
func (h *mheap) init() {
	h.pages.init(&h.lock, &memstats.gcMiscSys)
}

相关元数据

  • summary: 用于查找可用内存块的摘要。
  • chunks: 标记使用和释放状态的位图。
// mpagealloc.go


// Page allocator.
//
// The page allocator manages mapped pages (defined by pageSize, NOT
// physPageSize) for allocation and re-use. It is embedded into mheap.
//
// Pages are managed using a bitmap that is sharded into chunks.
// In the bitmap, 1 means in-use, and 0 means free.
//
// Each entry in the radix tree is a summary that describes three properties of
// a particular region of the address space: the number of contiguous free pages
// at the start and end of the region it represents, and the maximum number of
// contiguous free pages found anywhere in that region.


type pageAlloc struct {
    // Radix tree of summaries.
	summary [summaryLevels][]pallocSum

	// chunks is a slice of bitmap chunks.
	chunks [1 << pallocChunksL1Bits]*[1 << pallocChunksL2Bits]pallocData
}

所谓摘要(summary),就是将内存块(chunk)的 start、end、max 打包成一个整数。

// mpagealloc.go

// pallocSum is a packed summary type which packs three numbers: start, max,
// and end into a single 8-byte value. 

type pallocSum uint64

func packPallocSum(start, max, end uint) pallocSum {
	return pallocSum((uint64(start) & (maxPackedValue - 1)) |
		((uint64(max) & (maxPackedValue - 1)) << logMaxPackedValue) |
		((uint64(end) & (maxPackedValue - 1)) << (2 * logMaxPackedValue)))
}

使用位图(pallocBits),意味着堆内部不再用 mspan 管理。

需要时,查找摘要,返回可用地址,标记相关位图即可。

// mpallocbits.go

// pageBits is a bitmap representing one bit per page in a palloc chunk.
type pageBits [pallocChunkPages / 64]uint64

// pallocBits is a bitmap that tracks page allocations for at most one
// palloc chunk.
type pallocBits pageBits

type pallocData struct {
	pallocBits                 // 内存分配位图。
	scavenged pageBits         // 物理释放位图。
}

分配

分配操作获取内存,返回 mspan 对象,并初始化相关属性。

// mheap.go

// alloc allocates a new span of npage pages from the GC'd heap.
func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan {
	s = h.allocSpan(npages, spanAllocHeap, spanclass)
	return s
}
// allocSpan allocates an mspan which owns npages worth of memory.
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {

	base, scav := uintptr(0), uintptr(0)

	if base == 0 {
        
        // 分配。
		base, scav = h.pages.alloc(npages)
        
        // 失败!扩张后重试。
		if base == 0 {
			var ok bool
			growth, ok = h.grow(npages)
			if !ok {
				return nil
			}
            
			base, scav = h.pages.alloc(npages)
		}
	}
    
    // 创建 mspan 管理对象。
	if s == nil {
		s = h.allocMSpanLocked()
	}

    // 发生内存扩张,积极释放闲置物理内存。
	if growth > 0 {
		// We just caused a heap growth, so scavenge down what will soon be used.
		h.pages.scavenge(todo)
	}

HaveSpan:
    
	// 初始化 mspan。
	s.init(base, npages)
	nbytes := npages * pageSize
    
	// 设置属性。
	s.spanclass = spanclass
	...
	s.gcmarkBits = newMarkBits(s.nelems)
	s.allocBits = newAllocBits(s.nelems)

	atomic.Store(&s.sweepgen, h.sweepgen)
	s.state.set(mSpanInUse)

	// 补上被释放的部分(相邻多块被合并后可能出现的状态)。
	if scav != 0 {
		// sysUsed all the pages that are actually available
		// in the span since some of them might be scavenged.
		sysUsed(unsafe.Pointer(base), nbytes)
	}

	// 填充反查表。
	h.setSpans(s.base(), npages, s)

	return s
}

核心是 pageAlloc.alloc 操作。

  • 基于摘要查找可用内存块。
  • 更新位图信息。
// mpagealloc.go

// alloc allocates npages worth of memory from the page heap, returning the base
// address for the allocation and the amount of scavenged memory in bytes
// contained in the region [base address, base address + npages*pageSize).

func (p *pageAlloc) alloc(npages uintptr) (addr uintptr, scav uintptr) {

	// 直接搜索。
	searchAddr := minOffAddr
	if pallocChunkPages-chunkPageIndex(p.searchAddr.addr()) >= uint(npages) {
		i := chunkIndex(p.searchAddr.addr())

		if max := p.summary[len(p.summary)-1][i].max(); max >= uint(npages) {
			j, searchIdx := p.chunkOf(i).find(npages, chunkPageIndex(p.searchAddr.addr()))            
			addr = chunkBase(i) + uintptr(j)*pageSize
            
			goto Found
		}
	}
    
	// 慢搜索。
	addr, searchAddr = p.find(npages)
	if addr == 0 {
		return 0, 0
	}
    
Found:
    
	// 标记使用和释放位图。
	scav = p.allocRange(addr, npages)

	return addr, scav
}
// allocRange marks the range of memory [base, base+npages*pageSize) as
// allocated. It also updates the summaries to reflect the newly-updated
// bitmap.

func (p *pageAlloc) allocRange(base, npages uintptr) uintptr {
	chunk = p.chunkOf(ec)
	scav += chunk.scavenged.popcntRange(0, ei+1)
    
    // 更新位图。
	chunk.allocRange(0, ei+1)

    // 更新摘要。
    p.update(base, npages, true, true)
    
	return uintptr(scav) * pageSize
}
// mpallocbits.go

// allocRange sets bits [i, i+n) in the bitmap to 1 and
// updates the scavenged bits appropriately.

func (m *pallocData) allocRange(i, n uint) {
	m.pallocBits.allocRange(i, n)
	m.scavenged.clearRange(i, n)
}

如没找到适用闲置块,则向操作系统申请。

// mpagealloc.go

func (p *pageAlloc) grow(base, size uintptr) {

	limit := alignUp(base+size, pallocChunkBytes)
	base = alignDown(base, pallocChunkBytes)

	// 申请新内存。
	p.sysGrow(base, limit)

	// 更新位图。
	for c := chunkIndex(base); c < chunkIndex(limit); c++ {
		if p.chunks[c.l1()] == nil {
			r := sysAlloc(unsafe.Sizeof(*p.chunks[0]), p.sysStat)
			atomic.StorepNoWB(unsafe.Pointer(&p.chunks[c.l1()]), r)
		}
		p.chunkOf(c).scavenged.setRange(0, pallocChunkPages)
	}

    // 更新摘要。
    p.update(base, size/pageSize, true, false)
}

回收

向堆归还内存块。

// mheap.go

// Free the span back into the heap.
func (h *mheap) freeSpan(s *mspan) {
	h.freeSpanLocked(s, spanAllocHeap)
}
func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) {

	// 标记内存被闲置。
	h.pages.free(s.base(), s.npages, false)

	// 释放 mspan 管理对象。
	s.state.set(mSpanDead)
	h.freeMSpanLocked(s)
}

所有内存都以元数据进行管理,所以释放也只需清除位图标记,同时更新摘要即可。

// mpagealloc.go

// free returns npages worth of memory starting at base back to the page heap.
func (p *pageAlloc) free(base, npages uintptr, scavenged bool) {

    // 更新位图。
	if npages == 1 {  // large
		p.chunkOf(i).free1(chunkPageIndex(base))
	} else {
		p.chunkOf(sc).free(si, ei+1-si)
	}
    
    // 更新摘要。
	p.update(base, npages, true, false)
}
// mpallocbits.go

// free frees the range [i, i+n) of pages in the pallocBits.
func (b *pallocBits) free(i, n uint) {
	(*pageBits)(b).clearRange(i, n)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文