返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

2.5.1 同步释放

发布于 2024-10-12 19:16:03 字数 4816 浏览 0 评论 0 收藏 0

调用 scavenge 主动释放闲置物理内存(RSS)。

1.13 主动释放过于激进,有点神经质。

两个前提:其一,向操作系统申请内存时,会将地址段保存到 mheap.alloc.inUse 内。

// mpagealloc.go

func (s *pageAlloc) grow(base, size uintptr) {
    limit := alignUp(base+size, pallocChunkBytes)
    base = alignDown(base, pallocChunkBytes)
    
    s.sysGrow(base, limit)
    s.inUse.add(makeAddrRange(base, limit))
}

其二,每轮释放操作前调用 scavengeStartGen 初始化相关状态,重点是复制 inUse 地址段。

GC 清理操作(sweepone)和 debug.FreeOSMemory(mheap.scavengeAll)都会调用 scavengeStartGen。

这也是异步释放得以执行的前提。

// mpagealloc.go

type pageAlloc struct {
    
    // inUse is a slice of ranges of address space which are
    // known by the page allocator to be currently in-use (passed
    // to grow).
    inUse addrRanges
    
    // scav stores the scavenger state.
    scav struct {
        // inUse is a slice of ranges of address space which have not
        // yet been looked at by the scavenger.
        inUse addrRanges

        // gen is the scavenge generation number.
        gen uint32
    }    
}

addrRange 表示单个地址段,addrRanges 使用切片存储多个地址段。

// mgcscavenge.go

// scavengeStartGen starts a new scavenge generation, resetting
// the scavenger's search space to the full in-use address space.

func (s *pageAlloc) scavengeStartGen() {
    
    // 覆盖式完整数据拷贝(slice copy)。
    s.inUse.cloneInto(&s.scav.inUse)
    
    s.scav.gen++
    s.scav.released = 0
}

接下来,依次取地址段尝试物理内存释放,直到满足需求。

// mgcscavenge.go

// scavenge scavenges nbytes worth of free pages, starting with the
// highest address first. 
//
// Returns the amount of memory scavenged in bytes.

func (s *pageAlloc) scavenge(nbytes uintptr, mayUnlock bool) uintptr {
    
    // 循环,确保释放足够内存。
    for released < nbytes {
        if addrs.size() == 0 {
            if addrs, gen = s.scavengeReserve(); addrs.size() == 0 {
                break
            }
        }
        
        r, a := s.scavengeOne(addrs, nbytes-released, mayUnlock)
        released += r
        addrs = a
    }
    
    // 放回剩余未处理片段。
    s.scavengeUnreserve(addrs, gen)
    
    return released
}

最终,调用 sysUnused 完成物理内存 “释放”。

// mgcscavenge.go

// scavengeOne walks over address range work until it finds
// a contiguous run of pages to scavenge. 

// Returns the number of bytes scavenged and the part of work
// which was not yet searched.

func (s *pageAlloc) scavengeOne(work addrRange, max uintptr, mayUnlock bool) (uintptr, addrRange) {

    chunk := s.chunkOf(candidateChunkIdx)
    base, npages := chunk.findScavengeCandidate(pallocChunkPages-1, minPages, maxPages)
    
    if npages > 0 {
        work.limit = offAddr{s.scavengeRangeLocked(candidateChunkIdx, base, npages)}
        return uintptr(npages) * pageSize, work
    }
    
	return 0, work
}
// scavengeRangeLocked scavenges the given region of memory.
// Returns the base address of the scavenged region.

func (s *pageAlloc) scavengeRangeLocked(ci chunkIdx, base, npages uint) uintptr {
    
    // 标记释放位图。
    s.chunkOf(ci).scavenged.setRange(base, npages)

    // 释放物理内存。
    addr := chunkBase(ci) + uintptr(base)*pageSize
    sysUnused(unsafe.Pointer(addr), uintptr(npages)*pageSize)
    
	return addr
}

从堆申请内存(mheap.allocSpan)时,其内部通过 mheap.pages.alloc 返回内存地址和已释放大小。

依此调用 sysUsed 补回被释放的物理内存。详情参考《内存分配.结构.堆内存管理》。

同时,pageAlloc.alloc 调用 allocRange 清除释放位图标记。

默认的主动释放,由堆内存扩张时引发。

发生堆扩张,并不意味着没有闲置内存,很大可能是有大小不符合预期的 “碎片” 存在。

如此,通过释放碎片的物理内存来减少占用,可避免碎片化负面效应。

当然,碎片空间如果大小合适,依然可被重用。

// mheap.go

func (h *mheap) grow(npage uintptr) bool {
    ...
    
    h.pages.grow(h.curArena.base, size)
    totalGrowth += size
    
    ...
    
    // 如果 “当前 RSS 值 + 准备使用大小 > 期望值“,引发主动释放(目标:碎片)。
    if retained := heapRetained(); retained+uint64(totalGrowth) > h.scavengeGoal {
        todo := totalGrowth
        h.pages.scavenge(todo, false)
    }
}

每次垃圾回收结束时(gcMarkTermination -> gcSetTriggerRatio -> gcPaceScavenger)更新 scavengeGoal。

全部释放

用户调用 debug.FreeOSMemory,会释放全部闲置物理内存。

// heap.go

func runtime_debug_freeOSMemory() {
    GC()
    mheap_.scavengeAll()
}
// heap.go

func (h *mheap) scavengeAll() {
    h.pages.scavengeStartGen()
    released := h.pages.scavenge(^uintptr(0), false)   // 最大值!
}

调试信息

调试信息丛 gctrace 剥离,改用 GODEBUG=scavenge=1,scavtrace=1

mgcscavenge.go printScavTrace

格式scav # KiB work, # KiB total, #% util

* 此次释放数量。

* 总计释放数量。

* 未释放内存中,正在使用部分占比。

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

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

发布评论

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