golang内存清理

栏目: Go · 发布时间: 5年前

内容简介:当垃圾标记完成后,接着就以内存块(span)为单位进行清理操作;其实这里会带来一个疑问:是不是要挨个检查所有的内存单元,然后在一一清理。在理解这个东西之前需要看看mspan的结构通过上面的源码可以看到有一个垃圾标记位图(gcmarkBits),垃圾回收器以此标记出可回收,也就是可被复用的内存位置(golang分配的内存块当在进行垃圾回收时,并不会直接归还给操作系统,而是完成清理后归还给central中间部件,以便缓存部件能够复用,减少频繁的从操作系统分配内存的性能损耗),由于gcmarkBits标记位图和a

一、清理

当垃圾标记完成后,接着就以内存块(span)为单位进行清理操作;其实这里会带来一个疑问:是不是要挨个检查所有的内存单元,然后在一一清理。在理解这个东西之前需要看看mspan的结构

type mspan struct{  // 代表一个内存块
   //...
   gcmarkBits *gcBits   // 标记位图; 对应的object标记为垃圾 等待清理
   allocBits      *gcBits  // 分配位图; 对应的内存块object使用情况
   //...
}

通过上面的源码可以看到有一个垃圾标记位图(gcmarkBits),垃圾回收器以此标记出可回收,也就是可被复用的内存位置(golang分配的内存块当在进行垃圾回收时,并不会直接归还给操作系统,而是完成清理后归还给central中间部件,以便缓存部件能够复用,减少频繁的从操作系统分配内存的性能损耗),由于gcmarkBits标记位图和allocBits分配位图两者相似性,可以通过直接将标记位图数据复制给分配位图即可实现垃圾清理工作,关于内存单元里遗留数据的清除与否可以交给分配操作来考虑:在gcmarkBits标记位图里面对应bit=1代表当前位置object已被标记为垃圾,allocBits分配位图中对应bit=1代表当前object位置已被使用。

接下来看看垃圾回收具体操作:

func (s *mspan) sweep(preserve bool) bool{
  spc := s.spanclass  // 内存块规格
  
  // 已标记的已分配object数量(不包含可回收部分)
  nalloc := uint16(s.countAlloc())
   
 // 需要判断下当前大小规格:因为小对象和大对象分配来源不一样,大对象归还给堆
 if spc.sizeclass() == 0 && nalloc == 0{
    freeToHeap = true
  }

  // 本次回收object数量 = 标记分配总数量 - 标记后分配数量
  nfreed := s.allocCount - nalloc
 
  // 内存垃圾清理 会带来内存块属性的调整
  s.allocCount = nalloc  // 去掉被回收的object部分
  s.freeindex = 0           // 空闲索引置0

 // 将标记位图的记录当成分配位图的内容 实现复制
 s.allocBits = s.gcmarkBits
 s.gcmarkBits = newMarkBits(s.nelems)

 // 小对象分配内存块直接归还给中间部件mcentral,以便复用给其他的mcache
 if nfreed > 0 && spc.sizeclass() != 0{
    res = mheap_.central[spc].mcentral.freeSpan(s, preserve, wasempty)
  } else if freeToHeap{  // 大对象被分配内存块直接归还heap堆
    mheap_.freeSpan(s, 1) 
  }
}

在上述代码可以看到针对不同的大小object,golang采用了不同的内存回收策略,一般来说小对象多使用频繁,故而采用将回收的内存块归还给中间部件mcentral以便其他的mcache使用,减少内存分配的频繁操作以及内存复用; 而大对象相对来说是比较少的分配的内存块比较大,那么就直接从heap堆分配,回收时直接归还给heap堆。接下来看看对应的组件的回收操作

func (c *mcentral) freeSpan(s *mspan, preserve bool, wasempty bool) bool{
  //...
  if preserve{ // 调整存储列表
    return false
  }

 if wasempty{
    c.empty.remove(s)
    c.nonempty.insert(s)
 }
  
  // 更新垃圾回收代龄(缓存部件mcache是作为垃圾回收的跟对象)
  atomic.Store(&s.sweepgen, mheap_.sweepgen)
  // 还存在已分配内存 则直接返回 不需要进行内存回收 上交heap
  if s.allocCount != 0{return false}

  // 内存全部回收  则需要对应的内存块上交给heap
  c.nonempty.remove(s)
  mheap_.freeSpan(s,0)
  //...
  return true
}

在前面关于小对象的回收 只提到将对应的分配内存归还给中间部件(mcentral),那当中间部件(mcentral)持有的内存块回收全部空间的话,则需要归还给heap堆,这样可以便于其他的中间部件(mcentral)使用,这样也保证资源平衡,使得不同大小规格的内存请求能充分已有的内存,而不是重新向操作系统申请。

比如mcentral1持有大量的闲置内存块,而此时mcentral2、mcentral3等因内存耗尽发出扩容申请,这时候就可能到heap向操作系统进行分配物理内存申请。这样会导致更多的内存被闲置、浪费;那么若是将mcentral1闲置内容归还给heap堆,进而能够转给mcentral2、mcentral3使用是不是可以带来内存复用,尽量减少上述问题的产生。可能上交的内存块存在遗留数据,这就需要分配操作来完成。

这里面有点需要注意:mcentral上交的是完整回收空间span,主要由于每个span仅服务一种size class大小规格的对象,若是将剩余空间转给其他的mcentral,就会带来切分的操作,这样更容易导致内存碎片化。

回收操作

func (h *mheap) freeSpan(s *mspan, acct int32){
  systemstack(func(){
    h.freeSpanLocked(s, true, true, 0) // 回收 并尝试合并
 })
}
golang内存清理

内存回收


以上所述就是小编给大家介绍的《golang内存清理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java核心技术·卷1:基础知识(原书第9版)

Java核心技术·卷1:基础知识(原书第9版)

(美)Cay S. Horstmann、(美)Gary Cornell / 周立新、陈波、叶乃文、邝劲筠、杜永萍 / 机械工业出版社 / 2013-11-1 / 119.00

Java领域最有影响力和价值的著作之一,拥有20多年教学与研究经验的资深Java技术专家撰写(获Jolt大奖),与《Java编程思想》齐名,10余年全球畅销不衰,广受好评。第9版根据JavaSE7全面更新,同时修正了第8版中的不足,系统全面讲解Java语言的核心概念、语法、重要特性和开发方法,包含大量案例,实践性强。 《Java核心技术·卷1:基础知识》共14章。第1章概述了Java语言与其......一起来看看 《Java核心技术·卷1:基础知识(原书第9版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码