垃圾回收的实践者-垃圾回收器
如果说垃圾回收算法是垃圾回收的方法论,那垃圾回收器就是垃圾回收的实践者。
没有万能的收集器,只有针对具体应用最合适的收集器。
就目前来说,CMS和G1这两款收集器相对复杂但使用广泛。
《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定,因此不同的厂商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大差别,不同的虚拟机一般也都会提供各种参数供用户根据自己的应用特点和要求组合出各个内存分代所使用的收集器。
在JDK 7 Update 4之后(在这个版本中正式提供了商用的G1收集器)、JDK 11正式发布之前,OracleJDK中的HotSpot虚拟机所包含的全部可用的垃圾收集器如下图:
如果两个收集器之间存在连线,就说明它们可以搭配使用
图中的两个JDK9表示这条连线在JDK9之后断开,不能再配合使用了
图中收集器所处的区域,则表示它是属于新生代收集器或是老年代收集器
单线程工作
必须暂停其他所有工作线程,直到收集结束(被称作Stop The World)
对于Stop The World带给用户的恶劣体验,hotspot虚拟机团队一直在优化与创新,因此诞生了诸多的垃圾收集器,但Serial收集器仍有着优于其他收集器的地方:
简单而高效
对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的
对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销而更加高效
Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择
ParNew并行收集器
ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如下图所示。
Parallel Scavenge收集器与ParNew收集器的共同点:
新生代收集器
基于标记复制算法
并行收集的多线程收集器
不同点:
Parallel Scavenge的特点是它的关注点与其他收集器不同
其它收集器尽可能地缩短垃圾收集时用户线程的停顿时间
而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量
什么是吞吐量:
处理器用于运行用户代码的时间与处理器总消耗时间的比值,如下图所示。
关注停顿时间适用于需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验
关注高吞吐量适用于最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务
至于Parallel Scavenge如何控制吞吐量,我就不深入了,基本上用不到。
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法。这个收集器的主要意义也是供客户端模式下的HotSpot虚拟机使用。
Serial Old收集器的工作过程如下图所示。
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记整理算法实现。在注重吞吐量或者处理器资源较为稀缺的场合,可以优先考虑Parallel Scavenge加Parallel Old收集器这个组
Parallel Old收集器的工作过程如下图所示。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
大部分Java应用都是基于B/S的,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。
Mark Sweep表示CMS收集器是基于标记清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤,包括:
初始标记(CMS initial mark):Stop The World
并发标记(CMS concurrent mark):no
重新标记(CMS remark):Stop The World
并发清除(CMS concurrent sweep):no
CMS工作过程如下图所示。
初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图,这个过程耗时较长但是可以与用户线程并发运行
重新标记阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长,但也远比并发标记阶段的时间短
并发清除阶段清理删除掉标记阶段判断的已死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的
CMS停顿时间短的原因(优点):
由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程并发运行,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的
CMS缺点:
CMS在并发阶段会占用一部分线程,虽然不会导致用户线程停顿,但会导致应用程序变慢
CMS收集器无法处理浮动垃圾(并发清楚阶段,由于程序还在运行会有新的垃圾产生,即浮动垃圾),只能等下一次垃圾回收处理
基于标记清除法,会产生大量内存碎片
G1将Region作为单次回收的最小单元,每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。
G1收集器的运作过程大致可划分为以下四个步骤:
初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,这个阶段需要停顿线程,但耗时很短。
并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。
最终标记:对用户线程做另一个短暂的暂停,用于处理并发标记结束后有引用变动的记录。
筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间,这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成。
G1的工作过程如下图所示。
如何选择合适的垃圾回收器?
一个垃圾收集器除了垃圾收集这个本职工作之外,它还要负责:
-
堆的管理与布局 -
对象的分配 -
与解释器的协作 -
与编译器的协作 -
与监控子系统协作
如何选择一款适合自己应用的收集器呢?主要是看应用程序的主要关注点是什么:
如果是数据分析、科学计算类的任务,目标是能尽快算出结果,那吞吐量就是主要关注点
如果是服务类或WEB应用,那停顿时间直接影响服务质量,严重的甚至会导致事务超时,这样延迟就是主要关注点
如果是客户端应用或者嵌入式应用,那垃圾收集的内存占用则是不可忽视的
内存占用
吞吐量
延迟
三者共同构成了一个“不可能三角”,即三个方面同时“完美”的收集器几乎是不可能的,一款优秀的收集器通常最多可以同时达成其中的两项。
前面讲的都是理论,实在太枯燥了,不妨先看一些关于GC的实用技巧。
首先,我们可以用如下命令查看当前版本的jvm默认使用的是什么垃圾回收器。
java -XX:+PrintCommandLineFlags -version
重点看如下输出结果。
-XX:InitialHeapSize=264737408 -XX:MaxHeapSize=4235798528
-XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
可以看到-XX:+UseParallelGC,这是因为JDK9之前server模式下默认使用的是Parallel Scavenge+Serial Old组合(我的是JDK8)。
如果想要使用G1怎么办?可以通过显示指定-XX:+UseG1GC。而UseG1GC是JDK9之后server模式下的默认值。
另外,还有两条打印GC日志的虚拟机参数如下:
-XX:+PrintGC
-XX:+PrintGCDetails
下面通过一个例子来运行一下看看。
首先,我的代码如下(就是一直循环创建对象)。
public class GCTest {
private Integer id=999;
private String name="zhangsan";
public static void createObjectByFor(long time) throws InterruptedException {
for (;;){
new GCTest();
Thread.sleep(time);
}
}
public static void main(String[] args) throws InterruptedException {
createObjectByFor(1L);
}
}
然后在idea中指定一下虚拟机参数,如下图所示。
运行程序,控制台打印出来的GC日志如下图所示。
这么多垃圾收集器,是不是看的有点脑壳疼,我也是。尤其是G1回收器,书上描述的内容特别多,经过我的一番吸收消化,删减不重要的部分,留下的都是精华,主要是掌握G1回收器的思想。
到这里关于垃圾回收器的分享就结束了,如果你能全部看完看懂,碾压面试官应该是没问题了,划重点:G1。