业界常见的垃圾回收算法有以下几种:
- 引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0时回收该对象。
- 优点:对象可以很快地被回收,不会出现内存耗尽或达到某个阀值时才回收。
- 缺点:不能很好地处理循环引用,而且实时维护引用计数,也有一定的代价。
- 代表语言:Python、PHP、Swift
- 标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记为”被引用”,没有被标记的进行回收。
- 优点:解决了引用计数的缺点。
- 缺点:需要STW,即要暂时停掉程序运行。
- 代表语言:Golang(其采用三色标记法)
- 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不同的回收算法和回收频率。
- 优点:回收性能好
- 缺点:算法复杂
- 代表语言: JAVA
循环引用问题:(对象之间互相引用,都不能回收)
|
|
public class MyObject { public Object ref = null; public static void main(String[] args) { MyObject myObject1 = new MyObject(); MyObject myObject2 = new MyObject(); myObject1.ref = myObject2; myObject2.ref = myObject1; myObject1 = null; myObject2 = null; } } |

上图为例,当代码执行完line7时,两个对象的引用计数均为2。此时将myObject1和myObject2分别置为null,以前一个对象为例,它的引用计数将减1。
若要满足垃圾回收的条件,需要清除myObject2中的ref这个引用,而要清除掉这个引用的前提条件是myObject2引用的对象被回收,可是该对象的引用计数也为1,因为myObject1.ref指向了它。以此类推,也就进入一种死循环的状态。
三色标记法:
前面介绍了对象标记状态的存储方式,还需要有一个标记队列来存放待标记的对象,可以简单想象成把对象从标记队列中取出,将对象的引用状态标记在span的gcmarkBits,把对象引用到的其他对象再放入队列中。
三色只是为了叙述上方便抽象出来的一种说法,实际上对象并没有颜色之分。这里的三色,对应了垃圾回收过程中对象的三种状态:
- 灰色:对象还在标记队列中等待
- 黑色:对象已被标记,gcmarkBits对应的位为1(该对象不会在本次GC中被清理)
- 白色:对象未被标记,gcmarkBits对应的位为0(该对象将会在本次GC中被清理)
例如,当前内存中有A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示:

初始状态下所有对象都是白色的。
接着开始扫描根对象a、b:

由于根对象引用了对象A、B,那么A、B变为灰色对象,接下来就开始分析灰色对象,分析A时,A没有引用其他对象很快就转入黑色,B引用了D,则B转入黑色的同时还需要将D转为灰色,进行接下来的分析。如下图所示:

上图中灰色对象只有D,由于D没有引用其他对象,所以D转入黑色。直到没有灰色对象时,标记过程结束:

最终,黑色的对象会被保留下来,白色对象会被回收掉。
来源:https://www.topgoer.cn/docs/gozhuanjia/chapter044.2-garbage_collection
Go 三色标记算法的正确流程
1. 初始状态
2. 标记阶段(Marking)
-
从灰色对象出发(而不是直接标记黑色):
-
重复上述过程,直到没有灰色对象:
-
此时,所有黑色对象是存活的(从根可达)。
-
所有白色对象是不可达的,可以被回收。
3. 清除阶段(Sweeping)
Go 垃圾回收(GC)的触发时机
Go 的垃圾回收(GC)是 并发执行的,但它的运行时机由 运行时(runtime) 动态决定,主要取决于以下条件:
1. 核心触发条件
(1)内存分配阈值触发(主要方式)
(2)定时强制触发(2 分钟未触发时)
(3)手动触发
|
|
runtime.GC() // 强制立即执行 GC(通常用于性能测试或内存分析) |
(4)系统内存不足时(Linux/Mac 的 sysmon 监控)
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - gc垃圾回收