上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
2.5.1 同步释放
调用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论