如果大家关注 JDK,会发现在频繁发布的 JDK 版本中,和垃圾回收相关的 JEP (DK Enhancement Proposals,Java 增强提案)越来越多了,垃圾回收(Garbage Collection, GC)正处于方兴未艾的阶 段。譬如,在 JEP-248 中 G1 替代了并行垃圾回收器成为 JVM 中默认的垃圾回收器,JEP-333 加入了实验 性质的 ZGC;最新的 JEP-189 引入了名为 Shenandoah GC 的垃圾回收器。
垃圾回收(Garbage Collection,GC)引起大家的关注,是从 1995 年 Java 发布后开始的。事实上,GC 作为计算机科学领域非常热的研究话题之一,最早可以追溯到 1959 年的夏天,起初是用用来简化 Lisp 内存管理的。在接下来 60 余年的时间里,通过 Cheney、Baker 等大师的不断努力,GC 的世界里出现 了标记清除、复制、分代、增量回收等一系列 GC 算法,基于这些算法,又出现了种类繁复的垃圾回收
- GC 的定义 首先我们来看一下什么是 GC。 GC 把程序不用的内存空间视为「垃圾」,(几乎所有的)GC 要做的就只有两件事:
- 找到内存空间里的垃圾,使其和活对象分开来。
- 回收垃圾对象的内存,使得程序可以重复使用这些内存。 GC 给我们带来的好处不言而喻,选择 GC 而不是手动释放资源的原因很简单:程序比人更可靠。即便是 C/C++这种没有 GC 的语言,也有类似 Boehm GC 这样的第三方库来实现内存的自动管理了。可以毫不 夸张地说,GC 已经是现代编程语言的标配。 GC 的流派 GC 从其底层实现方式(即 GC 算法)来看,大体可以分为两大类:基于可达性分析的 GC 和基于引用计 数法的 GC。当然,这样的分类也不是绝对的,很多现代 GC 的设计就融合了引用计数和可达性分析两 种。 可达性分析法 基本思路就是通过根集合(gC root)作为起始点,从这些节点出发,根据引用关系开始搜索,所经过的 路径称为引用链,当一个对象没有被任何引用链访问到时,则证明此对象是不活跃的,可以被回收。使 用此类算法的有 JVM、.NET、Golang 等。
引用计数法 引用计数法没有用到根集概念。其基本原理是:在堆内存中分配对象时,会为对象分配一段额外的空 间,这个空间用于维护一个计数器,如果有一个新的引用指向这个对象,则计数器的值加 1;如果指向该 对象的引用被置空或指向其它对象,则计数器的值减 1。每次有一个新的引用指向这个对象时,计数器加 1;反之,如果指向该对象的引用被置空或指向其它对象,则计数器减 1;当计数器的值为 0 时,则自动 删除这个对象。使用此类算法的有 Python、Objective-C、Perl 等。 基于可达性分析法的 GC 垃圾回收的效率较高,实现起来比较简单(引用计算法是算法简单,实现较 难),但是其缺点在于 GC 期间,整个应用需要被挂起(STW,Stop-the-world,下同),后面很多此类 算法的提出,都是在解决这个问题(缩小 STW 时间)。