一、摘要
在之前的几篇文章中,我们介绍了 JVM 内部布局、对象的创建过程、运行期的相关优化手段以及垃圾对象的回收算法等相关知识。
今天通过这篇文章,结合之前的知识,我们一起来了解一下 JVM 中的垃圾收集器。
二、垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
不同的虚拟机所提供的垃圾收集器可能会有很大差异,以 HotSpot 虚拟机为例,所包含的垃圾收集器可以用如下图来概括。
上图中的连线表示,不同分代的收集器可以搭配使用。
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、CMS、Parallel Old
- 通用收集器: G1
在虚拟机中,没有所谓的万能收集器,只有根据具体的业务场景,选择最合适的收集器。这也是为什么 HotSpot 实现了这么多收集器的原因。
下面我们一起来看看相关的具体实现。
2.1、Serial 和 Serial Old收集器
Serial 系列的垃圾收集器是 JVM 的第一款收集器,它的设计思路很简单,在新生代,使用单线程采用复制算法进行收集对象;在老年代,使用单线程采用标记整理算法进行收集对象;垃圾收集的过程中会暂停用户线程,直到垃圾收集完毕。
因为当时的硬件环境配置都不高,内存都是几十兆,CPU 也都是单核的,不像现在这样处处都是高并发的应用场景。限于当时的硬件资源和应用场景,这个收集器优势很突出,简单高效、消耗资源也很少。
唯一的不足在于,在用户不可见的情况下要把用户正常工作的线程全部停掉,这对很多应用比较难以接受。不过实际上到目前为止,Serial 收集器依然是虚拟机在 Client 模式下运行的默认新生代收集器,因为它简单而高效。客户端应用模型下,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代对象,停顿时间平均在几十毫秒,只要不是频繁收集,完全可以接受。
整个流程,可以用如下图来概括。
总结下来,收集器特点如下:
- 收集区域: Serial(新生代),Serial Old(老年代)
- 收集算法: Serial(复制算法),Serial Old(标记整理算法)
- 收集方式:单线程
- 优势:简单高效,内存资源占用少,单核 CPU 环境最佳选项
- 劣势:整个搜集过程需要停顿用户线程,多核 CPU、大内存的环境,资源优势无法发挥起来
2.2、ParNew收集器
ParNew 收集器,可以看成是 Serial 收集器的多线程版本。除了使用多线程进行垃圾收集外,其余行为和 Serial 收集器完全一样,包括使用的也是复制算法,垃圾收集时暂停用户线程。在多核 CPU 资源环境下,可以显著提升整个垃圾收集的性能,也是虚拟机在 Server 模式下运行的首选新生代收集器。
能让 ParNew 出名的一个核心因素是,它是除了 Serial 收集器外,目前唯一一个能与 CMS 收集器配合一起使用的新生代收集器,因为 CMS 优秀所以 ParNew 也出名了,有点类似碰上了大款的感觉,其中 CMS 收集器是一款几乎可以认为有划时代意义的垃圾收集器,下文我们再讲。
其次,ParNew 收集器在单个 CPU 的环境中绝对不会有比 Serial 收集器更好的效果,甚至由于线程交互的开销,该收集器在两个 CPU 的环境中都不能百分之百保证可以超越 Serial 收集器。当然,随着可用 CPU 数量的增加,它对于垃圾收集的效率提升还是很有帮助的。
整个流程,可以用如下图来概括。
总结下来,收集器特点如下:
- 收集区域:新生代
- 收集算法:复制算法
- 收集方式:多线程
- 优势:多线程收集,多核 CP