-
-
并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。
-
并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。
3.5.1 Serial收集器
-
最基础,历史悠久
-
单线程
-
强调它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束(STW)
-
简单而高效,额外内存消耗最小的
-
对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
3.5.2 ParNew收集器
-
ParNew收集器实质上是Serial收集器的多线程并行版本
-
ParNew收集器在单核心处理器的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销
3.5.3 Parallel Scavenge收集器
-
一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器
-
Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值
-
运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)
-
吞吐量优先收集器
-
重要特性自适应调节策略
3.5.4 Serial Old收集器
-
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
-
一种是在JDK 5以及之前的版本中与Parallel Scavenge收集器搭配使用
-
另外一种就是作为CMS收集器发生失败时的后备预案
3.5.5 Parallel Old收集器
-
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
-
在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合
-
JDK8默认 PS/PO
3.5.6 CMS收集器
-
基于“标记-清除”算法实现
-
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
-
-
初始标记(CMS initial mark)初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,包含STW
-
并发标记(CMS concurrent mark)并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
-
重新标记(CMS remark)为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,包含STW
-
并发清除(CMS concurrent sweep)清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的
-
-
并发收集、低停顿
-
无法处理“浮动垃圾”,用户线程与标记过程同时进行产生的标记垃圾,只能在下一次清理。
3.5.6 G1收集器
-
G1是一款主要面向服务端应用的垃圾收集器。
-
JDK9发布日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器
-
它可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
-
G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。
-
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。
-
对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待
-
G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。
-
Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
-
G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,优先处理回收价值收益最大的那些Region。
-
可以由用户指定期望的停顿时间是G1收集器很强大的一个功能
-
G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现
3.6 低延迟垃圾收集器
衡量垃圾收集器的三项重要指标
-
内存占用
-
吞吐量
-
延迟
3.6.1 Shenandoah收集器
-
它与G1至少有三个明显的不同之处,最重要的当然是支持并发的整理算法
-
Shenandoah(目前)是默认不使用分代收集的
-
摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗
3.6.2 ZGC收集器
-
都希望在尽可能对吞吐量影响不太大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟
-
ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
3.7 选择合适的垃圾收集器
3.7.1 Epsilon收集器
-
这是一款以不能够进行垃圾收集为“卖点”的垃圾收集器
3.7.2 收集器的权衡
如何选择适合自己应用的收集器
-
应用程序的主要关注点是什么?如果是数据分析、科学计算类的任务,目标是能尽快算出结果,那吞吐量就是主要关注点;如果是SLA应用,那停顿时间直接影响服务质量,严重的甚至会导致事务超时,这样延迟就是主要关注点;而如果是客户端应用或者嵌入式应用,那垃圾收集的内存占用则是不可忽视的。
-
运行应用的基础设施如何?譬如硬件规格,要涉及的系统架构是x86-32/64、SPARC还是ARM/Aarch64;处理器的数量多少,分配内存的大小;选择的操作系统是Linux、Solaris还是Windows等。
-
使用JDK的发行商是什么?版本号是多少?是ZingJDK/Zulu、OracleJDK、Open-JDK、OpenJ9抑或是其他公司的发行版?该JDK对应了《Java虚拟机规范》的哪个版本
假设某个直接面向用户提供服务的B/S系统准备选择垃圾收集器,一般来说延迟时间是这类应用的主要关注点
-
如果你有充足的预算但没有太多调优经验,那么一套带商业技术支持的专有硬件或者软件解决方案是不错的选择,Azul公司以前主推的Vega系统和现在主推的Zing VM是这方面的代表,这样你就可以使用传说中的C4收集器了。
-
如果你虽然没有足够预算去使用商业解决方案,但能够掌控软硬件型号,使用较新的版本,同时又特别注重延迟,那ZGC很值得尝试。
-
如果你对还处于实验状态的收集器的稳定性有所顾虑,或者应用必须运行在Win-dows操作系统下,那ZGC就无缘了,试试Shenandoah吧。
-
如果你接手的是遗留系统,软硬件基础设施和JDK版本都比较落后,那就根据内存规模衡量一下,对于大概4GB到6GB以下的堆内存,CMS一般能处理得比较好,而对于更大的堆内存,可重点考察一下G1。