Go的内存管理|青训营笔记

31 阅读3分钟

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

自动内存管理

    与Java一样,Goland也有垃圾回收机制,即自动内存管理,程序在运行时会根据需求动态分配的内存,自动管理内存的调度。

自动内存管理可以让程序员专注于业务的实现,也能够保证内存使用的正确性和安全性

相关概念

  • Grabage collction: 垃圾回收
  • Mutator: 业务线程
  • Collector: GC 线程
  • Concurrent GC: 并发 GC

衡量一个垃圾回收算法好坏的指标

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

常见的垃圾回收算法

引用计数算法

每个对象都有一个与之关联的引用数目
对象存活的条件:当且仅当引用数大于 0,当小于0时,对象会被回收

优点

内存管理的操作被平摊到程序运行中:指针传递的过程中进行引用计数的增减

不需要了解 runtime 的细节:因为不需要标记 GC roots,因此不需要知道哪里是全局变量、线程栈等

缺点

开销大,因为对象可能会被多线程访问,对引用计数的修改需要原子操作保证原子性和可见性

无法回收环形数据结构,假设有一个互相指向的环形链表,各自的引用数量都不为0,所以无法被回收,占用了系统资源

每个对象都引入额外存储空间存储引用计数

虽然引用计数的操作被平摊到程序运行过程中,但是回收大的数据结构依然可能引发暂停,导致用户的体验变差

追踪垃圾回收

过程

标记根对象 : 静态变量、全局变量、常量、线程栈等

标记:找到所有可达对象

清理:回收所有不可达对象占据的内存空间

清理分为三个阶段

1.将存活对象复制到另外的内存空间(Copying GC),原先的空间可以直接进行对象分配

2.将死亡对象的内存标记为"可分配"(Mark-sweep GC),使用 free list 管理可分配的空间

3.移动并整理存活对象(Mark-compact GC)

分代GC

将对象分为新生代和和老年代

新生代的对象创建的数量多,被回收的数量也多,系统需要频繁地去管理,根据这个特性,可以对新生代的对象采用复制算法

即将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

老年代的对象不会立刻被回收,经过了多次的新生代的回收算法后还存活,较为稳定,不需要频繁整理

因此对老年代使用: 标记 - 清除 或者 标记 - 整理 算法

标记-清除算法: 将存活的对象进行标记,然后清理掉未被标记的对象。

标记和清除过程效率都不高;

会产生大量不连续的内存碎片,导致无法给大对象分配内存

标记-整理算法: 让所有存活的对象都向一端移动,然后直接清理掉另一端的内存。

第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象。

第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。