图中展示了七种作用于不同分代的收集器, 如果两个收集器之间存在连线, 就说明它们可以搭配使用, 图中收集器所处的区域, 则表示它是属于新生代收集器抑或是老年代收集器。
一个单线程工作的收集器, 但它的 “单线程” 的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作, 更重要的是强调在它进行垃圾收集时, 必须暂停其他所有工作线程(Stop the world), 直到它收集结束。迄今为止, 它依然是 HotSpot 虚拟机运行在客户端模式下的默认新生代收集器。
优点:
缺点: 单线程的垃圾收集器,在进行垃圾收集时会导致STW。
ParNew 收集器实质上是 Serial 收集器的多线程并行版本。ParNew 收集器的工作过程如图所示:
ParNew 收集器除了支持多线程并行收集之外, 其他与 Serial 收集器相比并没有太多创新之处, 但它却是不少运行在服务端模式下的 HotSpot 虚拟机, 尤其是 JDK 7 之前的遗留系统中首选的新生代收集器, 其中有一个与功能 、 性能无关但其实很重要的原因是:除了Serial 收集器外, 目前只有它能与 CMS 收集器配合工作。
特点:
Parallel Scavenge 收集器也是一款新生代收集器, 它同样是基于标记 - 复制算法实现的收集器, 也是能够并行收集的多线程收集器。Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。 所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
Parallel Scavenge 收集器也经常被称作 “吞吐量优先收集器” 。
特点: Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同, CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间, 而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。
Serial Old 是 Serial 收集器的老年代版本, 它同样是一个单线程收集器, 使用标记 - 整理算法。 这个收集器的主要意义也是供客户端模式下的 HotSpot 虚拟机使用。 如果在服务端模式下, 它也可能有两种用途: 一种是在 JDK 5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用, 另外一种就是作为 CMS 收集器发生失败时的后备预案, 在并发收集发生Concurrent Mode Failure 时使用。
Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 支持多线程并发收集, 基于标记 - 整理算法实现。
这个收集器是直到 JDK 6 时才开始提供的, 在此之前, 新生代的 Parallel Scavenge 收集器一直处于相当尴尬的状态, 原因是如果新生代选择了 Parallel Scavenge 收集器, 老年代除了 Serial Old ( PS MarkSweep ) 收集器以外别无选择, 其他表现
良好的老年代收集器, 如 CMS 无法与它配合工作。 由于老年代 Serial Old 收集器在服务端应用性能上的 “拖累” , 使用 Parallel Scavenge 收集器也未必能在整体上获得吞吐量最大化的效果。 同样, 由于单线程的老年代收集中无法充分利用服务器多处理器的并行处理能力,在老年代内存空间很大而且硬件规格比较高级的运行环境中, 这种组合的总吞吐量甚至不
一定比 ParNew 加 CMS 的组合来得优秀。
直到 Parallel Old 收集器出现后, “吞吐量优先” 收集器终于有了比较名副其实的搭配组合, 在注重吞吐量或者处理器资源较为稀缺的场合, 都可以优先考虑 Parallel Scavenge 加Parallel Old 收集器这个组合。 Parallel Old 收集器的工作过程如图所示:
特点:Parallel Old 是 Parallel Scavenge 收集器的老年代版本, 支持多线程并发收集, 基于标记 - 整理算法实现。
Parallel Scavenge与Parallel Old组合使用。
CMS ( Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。
目前很大一部分的 Java 应用集中在互联网网站或者基于浏览器的 B/S 系统的服务端上, 这类应用通常都会较为关注服务的响应速度, 希望系统停顿时间尽可能短, 以给用户带来良好的交互体验。 CMS 收集器就非常符合这类应用的需求。
整个过程分为四个步骤:
由于在整个过程中耗时最长的并发标记和并发清除阶段中, 垃圾收集器线程都可以与用户线程一起工作, 所以从总体上来说, CMS 收集器的内存回收过程是与用户线程一起并发执行的。 其执行过程如图所示:
优点:
缺点:
Garbage First (简称 G1 ) 收集器是垃圾收集器技术发展历史上的里程碑式的成果,开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。
G1 是一款主要面向服务端应用的垃圾收集器。 JDK 9 发布之日, G1 宣告取代 Parallel Scavenge 加 Parallel Old 组合, 成为服务端模式下的默认垃圾收集器, 而 CMS 则沦落至被声明为不推荐使用( Deprecate) 的收集器。
G1 满足 “停顿时间模型”( Pause Prediction Model ) 的收集器, 停顿时间模型的意思是能够支持指定在一个长度为M 毫秒的时间片段内, 消耗在垃圾收集上的时间大概率不超过 N 毫秒这样的目标.
G1 可以面向堆内存任何部分来组成回收集 (Collection Set, 一般简称 CSet) 进行回收, 衡量标准不再是它属于哪个分代, 而是哪块内存中存放的垃圾数量最多, 回收收益最大, 这就是 G1 收集器的 Mixed GC 模式。
G1 开创的基于 Region 的堆内存布局是它能够实现这个目标的关键。 虽然 G1 也仍是遵循分代收集理论设计的, 但其堆内存的布局与其他收集器有非常明显的差异: G1 不再坚持固定大小以及固定数量的分代区域划分, 而是把连续的 Java 堆划分为多个大小相等的独立区域( Region), 每一个 Region 都可以根据需要, 扮演新生代的 Eden 空间、 Survivor 空间,或者老年代空间。 收集器能够对扮演不同角色的 Region 采用不同的策略去处理, 这样无论是新创建的对象还是已经存活了一段时间、 熬过多次收集的旧对象都能获取很好的收集效果。
Region 中还有一类特殊的 Humongous 区域, 专门用来存储大对象。 G1 认为只要大小超过了一个 Region 容量一半的对象即可判定为大对象。
虽然 G1 仍然保留新生代和老年代的概念, 但新生代和老年代不再是固定的了, 它们都是一系列区域(不需要连续) 的动态集合。 G1 收集器之所以能建立可预测的停顿时间模型,是因为它将 Region 作为单次回收的最小单元, 即每次收集到的内存空间都是 Region 大小的整数倍, 这样可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集, 思路是让 G1 收集器去跟踪各个 Region 里面的垃圾堆积的 “价值” 大小, 价值即回收所获得的空间大小以及回收所需时间的经验值, 然后在后台维护一个优先级列表, 每次根据用户设定允许的收集停顿时间( 使用参数-XX:MaxGCPauseMillis 指定, 默认值是 200 毫秒),优先处理回收价值收益最大的那些 Region, 这也就是 “ Garbage First” 名字的由来。 这种使用 Region 划分内存空间, 以及具有优先级的区域回收方式, 保证了 G1 收集器在有限的时间内获取尽可能高的收集效率。
G1 收集器除了并发标记外, 其余阶段也是要完全暂停用户线程的, 换言之, 它并非纯粹地追求低延迟, 官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量, 所以才能担当起 “全功能收集器” 的重任与期望。
G1 收集器的运作步骤中并发和需要停顿的阶段如图所示:
可以由用户指定期望的停顿时间是 G1 收集器很强大的一个功能, 设置不同的期望停顿时间, 可使得 G1 在不同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。 但是也不能设置太短,如果我们把停顿时间调得非常低, 譬如设置为二十毫秒, 很可能出现的结果就是由于停顿目标时间太短,导致每次选出来的回收集只占堆内存很小的一部分, 收集器收集的速度逐渐跟不上分配器分配的速度, 导致垃圾慢慢堆积。 很可能一开始收集器还能从空闲的堆内存中获得一些喘息的时间, 但应用运行时间一长就不行了, 最终占满堆引发 Full GC 反而降低性能。
优点:
可以指定最大停顿时间、 分 Region 的内存布局、 按收益动态确定回收集。
Mixed GC模式。可以面向堆内存任何部分来组成回收集进行回收, 衡量标准不再是它属于哪个分代, 而是哪块内存中存放的垃圾数量最多, 回收收益最大, 这就是 G1 收集器的 Mixed GC 模式。
G1 将 Region 作为单次回收的最小单元, 即每次收集到的内存空间都是 Region 大小的整数倍, 这样可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。
不会产生内碎片。与 CMS 的 “标记 - 清除” 算法不同, G1 从整体来看是基于 “标记-整理” 算法实现的收集器, 但从局部( 两个 Region 之间) 上看又是基于 “标记- 复制” 算法实现, 无论如何, 这两种算法都意味着 G1 运作期间不会产生内存空间碎片, 垃圾收集完成之后能提供规整的可用内存。 这种特性有利于程序长时间运行, 在程序为大对象分配内存时不容易因无法找到连续内存空间而提前触发下一次收集。
具有优先级的区域回收;用户可以设置收集停顿时间,优先处理回收价值收益最大的那些 Region, 式, 保证了 G1 收集器在有限的时间内获取尽可能高的收集效率。
缺点:
在用户程序运行过程中, G1 无论是为了垃圾收集产生的内存占用(Footprint) 还是程序运行时的额外执行负载 (Overload) 都要比 CMS 要高。
在小内存应用上 CMS 的表现大概率仍然要会优于 G1 , 而在大内存应用上 G1 则大多能发挥其优势, 这个优劣势的 Java 堆容量平衡点通常在 6GB 至8GB 之间,
参考:《深入理解Java虚拟机第三版 周志明》