内容简介:一般在golang运行完成初始化时,会创建专门的goroutine用于后台监控、定期任务,这其中也涉及到了强制垃圾回收、内存释放等任务。从上面的源码可以看到在运行初始化期间通过newm(sysmon,nil)来开启一些系统监控。接下来看看sysmon的源码在进行内存释放时,其实针对的是闲置内存(被堆heap管理、尚未被中间部件mcentral或大对象使用的内存块),而这些内存有可能长时间不使用,那么就应该释放掉其占有的物理内存,节约系统资源。在golang本身对内存管理对象使用两个计数器:unusedsin
一、前言
一般在golang运行完成初始化时,会创建专门的goroutine用于后台监控、定期任务,这其中也涉及到了强制垃圾回收、内存释放等任务。
// 主goroutine. func main() { // ... // m0: 系统主线程 // g0:主goroutine // m0、g0是比较特殊的 仅用于main goroutine的父goroutine g.m.g0.racectx = 0 if sys.PtrSize == 8 { // 64bits 系统 maxstacksize = 1000000000 } else { // 32bits系统 maxstacksize = 250000000 } // 新建M(物理线程) mainStarted = true if GOARCH != "wasm" { // 没有线程在wasm 没必要进行系统监控的 systemstack(func() { newm(sysmon, nil) }) } // 在初始化时将main goroutine与系统主线程锁定 lockOSThread() if g.m != &m0 { throw("runtime.main not on m0") } runtime_init() // 初始化 if nanotime() == 0 { throw("nanotime returning zero") } // ... }
从上面的源码可以看到在运行初始化期间通过newm(sysmon,nil)来开启一些系统监控。接下来看看sysmon的源码
// 通常该方法执行时没有关联的P(上下文环境) 以至于写屏蔽的是不允许的 // 一般来说golang中goroutine都会有与之关联的P记录上下文 func sysmon() { // ... // 当一块堆内存块在一次垃圾回收后5分钟没有被使用 则会被归还操作系统 scavengelimit := int64(5 * 60 * 1e9) // 堆内存归还给操作系统时限: 5分钟 if debug.scavenge > 0 { // Scavenge-a-lot for testing. forcegcperiod = 10 * 1e6 scavengelimit = 20 * 1e6 } lastscavenge := nanotime() // 最后执行时间 nscavenge := 0 // 执行次数统计 lasttrace := int64(0) // 最近一次追踪 idle := 0 // 记录没有唤醒的次数 delay := uint32(0) for { if idle == 0 { // 默认延迟20us delay = 20 } else if idle > 50 { // 当超过1ms 延迟时间加倍 *2 delay *= 2 } // 延迟最大=10ms(当延迟时间超过了10ms) if delay > 10*1000 { delay = 10 * 1000 } usleep(delay) // 延迟delay执行GC now := nanotime() // ... // 是否需要强制GC if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 { lock(&forcegc.lock) forcegc.idle = 0 forcegc.g.schedlink = 0 injectglist(forcegc.g) unlock(&forcegc.lock) } // 检查并释放物理内存 if lastscavenge+scavengelimit/2 < now { mheap_.scavenge(int32(nscavenge), uint64(now), uint64(scavengelimit)) lastscavenge = now nscavenge++ } // ...... } }
二、闲置内存
在进行内存释放时,其实针对的是闲置内存(被堆heap管理、尚未被中间部件mcentral或大对象使用的内存块),而这些内存有可能长时间不使用,那么就应该释放掉其占有的物理内存,节约系统资源。在golang本身对内存管理对象使用两个计数器:unusedsince闲置起始时间 npreleased释放归还os的页数
type mspan struct{ unusedsince int64 // 首次被发现当前span状态=mspanfree npreleased uintptr // 归还给os的页数 }
其中内存块获取和归还操作时内存管理对象的计数器会被重置
// 根据指定大小分配空间,新分配的span会从freelist被移除代表该span已被使用 // 但是该span状态仍是=mspanfree(这一点需要注意) func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan{ // ......省略代码 HaveSpan: // 刚被分配的span 状态=mspanfree if s.state != _MSpanFree { throw("MHeap_AllocLocked - MSpan not free") } if s.npages < npage { throw("MHeap_AllocLocked - bad npages") } if s.npreleased > 0 { // 已使用空间span sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift) // 内存统计:堆heap释放的内存 memstats.heap_released -= uint64(s.npreleased << _PageShift) s.npreleased = 0 } if s.npages > npage { // 申请的空间span页数 低于该空间的页数 需要进行裁剪 // 进行多余空间裁剪 并归还给heap堆 t := (*mspan)(h.spanalloc.alloc()) // 更新裁剪的span t.init(s.base()+npage<<_PageShift, s.npages-npage) s.npages = npage p := (t.base() - h.arena_start) >> _PageShift if p > 0 { h.spans[p-1] = s } h.spans[p] = t h.spans[p+t.npages-1] = t t.needzero = s.needzero s.state = _MSpanManual // 防止与s结合 t.state = _MSpanManual h.freeSpanLocked(t, false, false, s.unusedsince) s.state = _MSpanFree } s.unusedsince = 0 // ......省略代码 }
// s:需要属于busy list或者没有任何引用 func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince int64){ // ......省略代码 // 标记最新未被使用的空间span // GC则会根据这些信息将一些页归还给OS s.unusedsince = unusedsince if unusedsince == 0 { s.unusedsince = nanotime() } s.npreleased = 0 // ......省略代码 }
在归还操作过程中,可能存在局部释放的情况:当内存空间释放了对应的物理内存,假设此时npreleased == npages,不过一旦该内存块与其他内存块进行合并,就会导致npreleased < npages.
2.1 释放
在内存分配过程中:<128页的可用内存是放置在free链表数组中,而>=128页的可用内存则是通过树堆freelarge来存储的,也就是说释放操作其实针对的就是这两个列表。
// 释放指定内存块 // 一旦仍持有堆heap锁 无论是malloc操作还是发生panic都不会产生 // 主要因为这是mheap接口的non-mallocgc入口 // now:超时判断的基准时间(首次被标记为垃圾的内存块的时间会与该时间进行比较) // limit: now - unusedsince与该超时阈值比较 超过都可释放(默认时间5分钟) func (h *mheap) scavenge(k int32, now, limit uint64) { // ......省略代码 var sumreleased uintptr for i := 0; i < len(h.free); i++ { sumreleased += scavengelist(&h.free[i], now, limit) } sumreleased += scavengetreap(h.freelarge.treap, now, limit) unlock(&h.lock) gp.m.mallocing-- // 输出统计结果 if debug.gctrace > 0 { if sumreleased > 0 { print("scvg", k, ": ", sumreleased>>20, " MB released\n") } // ......省略代码 } }
真正的比较操作,确认符合释放要求的内存块
func scavengelist(list *mSpanList, now, limit uint64) uintptr { if list.isEmpty() { // 跳过空链表 return 0 } var sumreleased uintptr // 遍历链表内所有的span for s := list.first; s != nil; s = s.next { // 忽略不符合释放条件的: 已被释放的、闲置时间小于limit的 if (now-uint64(s.unusedsince)) <= limit || s.npreleased == s.npages { continue } // 统计要释放的空间 start := s.base() end := start + s.npages<<_PageShift // 物理页大小 过大,超过指定的系统页大小 // 需要保证释放范围end-start在物理页内存块范围内 // 否则可能超过所需要释放的范围 超出我们实际需要的释放空间 if physPageSize > _PageSize { start = (start + physPageSize - 1) &^ (physPageSize - 1) end &^= physPageSize - 1 if end <= start { // 忽略持续整个物理页的span continue } } len := end - start // 要释放的空间大小 released := len - (s.npreleased << _PageShift) if physPageSize > _PageSize && released == 0 { continue } memstats.heap_released += uint64(released) sumreleased += released // 释放计数 s.npreleased = len >> _PageShift // 释放物理内存(整块内存) sysUnused(unsafe.Pointer(start), len) } return sumreleased }
而释放树堆freelarge里面的内存块,基本操作一致。到此那些内存可被释放?如何释放其物理内存等基本上有所了解,具体的释放因操作系统不同而异。
Unix类似的系统基本上都是通过madvise来建议内核解除物理内存映射,这样在保留虚拟内存的情况下,达到释放物理内存的目的。当这些内存被使用时,有内存来自动补齐对应所需的物理内存。
windows则不支持类似的机制,直接通过对应的系统API进行释放和重新分配的。
内存释放
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【缺陷周话】第22期:错误的内存释放对象
- 【缺陷周话】第31期:错误的内存释放方法
- Go的map中删除子map,内存会自动释放吗?
- cocoa-touch – 使用dismissModalViewControllerAnimated不会释放任何内存
- sql-server – 如何释放空闲SQL Server数据库使用的内存?
- golang中的定时器由于没有正确释放导致内存和cpu使用率异常
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
文本上的算法——深入浅出自然语言处理
路彦雄 / 人民邮电出版社 / 2018-3-1 / 69.00元
本书结合作者多年学习和从事自然语言处理相关工作的经验,力图用生动形象的方式深入浅出地介绍自然语言处理的理论、方法和技术。本书抛弃掉繁琐的证明,提取出算法的核心,帮助读者尽快地掌握自然语言处理所必备的知识和技能。本书主要分两大部分。第一部分是理论篇,包含前3章内容,主要介绍一些基础的数学知识、优化理论知识和一些机器学习的相关知识。第二部分是应用篇,包含第4章到第8章,分别针对计算性能、文本处理的术语......一起来看看 《文本上的算法——深入浅出自然语言处理》 这本书的介绍吧!