Go的内存管理和垃圾回收| 青训营笔记

104 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第4天

自动内存管理

所谓自动内存管理,其实就是指垃圾回收,在 Go 中,程序在运行时根据需求动态分配的内存(即动态内存)会被纳入自动内存管理的范畴。

动态内存

程序在运行时根据需求动态分配的内存

自动内存管理(垃圾回收)

有程序语言运行时系统回收动态内存。

  • ·避免手动内存管理,专注实现业务逻辑
  • ·保证内存使用的正确性和安全性

内存回收三大任务

·为新对象分配空间 ·找到存活的对象 ·回收死亡对象的内存空间‘’

一些相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系。
  • Collector:GC线程,找到存货对象,回收死亡对象的业内物空间
  • Serial GC: 只有一个collector线程 会暂停其他Mutator线程
  • Parallel GC: 支持多个collector线程同时回收的GC算法
  • Concurrent GC: mutator和collector可以同时执行

评价GC算法的几个方面

  • 安全性(Safety):指垃圾回收器不应回收存活的对象
  • 吞吐率(Throughput):指垃圾回收器花在 GC 上的时间占程序执行总时间的比率
  • 暂停时间(Pause time):指垃圾回收导致业务线程挂起(暂停)的时间(GC 导致的暂停被称为 stop the world, STW)
  • 内存开销(Space overhead):指垃圾回收器元数据占用的内存开销

追踪垃圾回收的步骤:

  • ·标记根对象(静态变量,全局变量,常量,线程栈等)
  • ·从根对象出发,找到所有可达对象
  • ·清理所有不可达对象

三种常见GC

分代GC(Generational GC)

与 Go 相同,Java 也支持垃圾回收,而 Java 目前主流的垃圾回收器 G1GC(Garbage First Garbage Collector),就是一种分代垃圾回收器(Generational GC)

分代 GC 的设计来源于分代假说(Generational hypothesis) —— most objects die young,即大多数对象在很短的生命周期内就会死亡,分配出来后很快就不再使用了。通过为年轻和年老(经历过 GC 的次数越多则越老,反之越年轻)的对象指定不同的 GC 策略,降低整体内存管理的开销。

对于年轻代(Young generation)的对象,可以采用 copying collection,且提高 GC 的吞吐率;对于老年代(Old generation)的对象则可以采用 mark-sweep collection。

追踪垃圾回收

追踪垃圾回收(Tracing Garge Collection) 是一种最常见的垃圾回收方式,它通过跟踪哪些对象可以通过来自某些“根”对象的引用链访问来确定哪些对象应该被释放(“垃圾回收”),并将其余对象视为“垃圾”并收集它们

追踪垃圾回收也是 Go 目前正在使用的垃圾回收算法。

简单来说,追踪垃圾回收以如下方式工作:

  1. 首先,标记根对象,这些根对象可能包括静态变量,全局变量,常量,线程栈等
  2. 然后,从根对象触发,找到所有引用根对象的可达对象
  3. 最后,清理所有不可达对象,这分为三个步骤:将存活对象复制到另外的内存空间(Copying GC),将死亡对象的内存标记为"可分配"(Mark-sweep GC),移动并整理存活对象(Mark-compact GC)。

根据对象的生命周期,垃圾回收器可能会使用不同的标记和清理策略。

引用计数

确定一个对象需要被回收的另一种方式是引用计数(Referenct counting) ,其为每一个对象维护一个与之关联的引用数目,当且仅当引用数大于 0 时,该对象才会被标记为存活,否则,对象会被回收。

引用计数方案的优点是,内存管理的操作被平摊到程序执行的过程中(当新建对象,或是将对象添加到一个集合中时增加引用计数,反之,销毁对象或是从集合中移除时减少引用计数),并且内存管理不需要了解 runtime 的实现细节(例如 C++ 的智能指针);

相反,其缺点就是维护引用计数的开销较大(因为引用计数操作必须是原子的),无法回收环形数据结构(因为所有对象都直接或间接的互相引用对方),每个对象引入额外的内存空间以存储引用数目,回收内存时依然可能引发暂停等。