之前面试的时候被面试官问到过这个问题,但是当时对Go的GC一点也不了解。于是后来整理了一下,发现Go的GC算法真有意思。本文对比了Java和Go在GC算法上的的区别:
Java GC
Java的GC使用分代回收算法,将堆内存划分为新生代和老年代,一般情况下在新生代使用标记-复制算法,老年代使用标记-清除或标记-整理算法
分代GC依赖分代假设,即GC将主要的回收目标放在新创建的对象上(存活时间短,更倾向于被回收),而非频繁检查所有对象。
在Java中,不同的垃圾回收器使用的垃圾回收算法不同:
- JDK 8:Parallel Scavenge
- JDK 9 ~ JDK20: G1
特点:
- 都会造成STW,这是影响性能最主要的因素
- 部分算法还会产生大量空间碎片
介绍一下Java中比较经典的垃圾回收器:
CMS
以获取最短回收停顿时间为目标,使用了标记-清除 算法,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
- 主要优点:并发收集、低停顿
- 缺点:对 CPU 资源敏感,会产生大量空间碎片
G1
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率)。
G1使用标记-整理算法
Go GC
Golang的垃圾回收算法使用的是无分代、不整理、并发的三色标记清扫算法**。原因在于:
- Go 运行时的分配算法基于
tcmalloc
,基本上没有碎片问题,对对象进行整理不会带来实质性的性能提升。 - Go 的编译器会通过逃逸分析将大部分新生对象存储在栈上,只有那些需要长期存在的对象才会被分配到需要进行垃圾回收的堆中。也就是说,分代
GC
回收的那些存活时间短的对象在 Go 中是直接被分配到栈上,当goroutine
死亡后栈也会被直接回收,不需要GC
的参与,进而分代假设并没有带来直接优势。 - 引入了混合屏障机制,能够让Go 的垃圾回收器部分阶段与用户代码并发执行,大大缩短了STW的时间