采访者:你认识的收藏家有哪些?答案:Serial,ParNew,ParallelScavenge,SerialOld,ParallelOld,CMS,G1;面试官:为什么HotSpot虚拟机需要那么多采集器?答:热点垃圾是分代收集的,所以使用不同代的收集器,即使是同一代,因为每个收集器都有不同的特性和性能。为了增加收集器的多样性,每个收集器可以组合使用以适应不同的场景。采访者:那说说每个收藏家的特点吧!新一代收集器:Serial、ParNew、ParallelScavenge;老年代收集器:SerialOld、ParallelOld、CMS;整堆收集器:G1;开始之前我们了解了一个概念MinorGC,也称为新生代GC,指的是发生在新生代的垃圾回收动作;由于大多数Java对象的诞生和消失,MinorGC非常频繁,回收速度普遍较快;FullGC也称为MajorGC或oldgenerationGC,指发生在老年代的GC;FullGC往往伴随着至少一次MinorGC(不是绝对的,ParallelSacvenge收集器可以选择设置MajorGC策略);MajorGC的速度一般比MinorGC慢10倍以上;Serial收集器是最基本也是最古老的收集器,目前基本不用。我觉得那是当年新生代唯一的选择,但是这么多年过去了,HotSpot也没说过河的桥被拆了,就被废弃了。“可惜它老了没用,吃起来没味道,被丢弃了。”他是一个单线程收集器。当他工作时,他必须暂停所有其他工作线程,直到收集结束。这里要知道一个很严重的问题是,挂起所有线程的结果是这个JDK的所有程序中当前运行的所有用户线程都被挂起,也就是说这一刻都死了,而用户出现的现象看到的是页面没有任何反应。如果这种现象长期频繁出现,用户就会死机。这里有个伏笔,越优秀的收藏家,他的停顿时间一定越短,这也是所有收藏家的共同目标。ParNew收集器是Serial收集器的多线程版本,其所有控制参数、收集算法、对象分配规则、回收策略等与Serial完全相同。下面是ParNew收集器的工作过程。它的重要性在于除了多线程之外,它还可以与CMS收集器(如下所述)一起使用以提高性能。在单CPU环境下,ParNew的性能无法超越Serial,但随着CPU数量的增加,其优势会越来越明显。ParallelScavenge收集器也是一种使用复制算法的新一代收集器,是一种并行线程收集器。可见收集器的进步就是保留上一代的长度,弥补上一代的不足。许多收集器关注用户线程暂停时间,但ParallelScavenge关注吞吐量。所谓吞吐量就是CPU运行用户代码的时间与CPU消耗的总时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾回收时间),例如:虚拟机运行100分钟,其中垃圾收集时间用了1分钟,所以吞吐率为99%。他如何控制吞吐量?使用参数控制最大垃圾回收暂停时间-XX:MaxGCPauseMillis,直接设置吞吐量大小-XX:GCTimeRatio参数。MaxGCPauseMillis参数是一个大于0的毫秒数,收集器尽可能工作不超过设定值,但如果设置太小,GC暂停时间会缩短,导致垃圾收集频率更快。如果设置100毫秒的停顿,10秒采集一次的频率,改成70毫秒的停顿时间,那么频率可能会变成5秒一次。暂停时间减少,吞吐量下降,GC变得更加频繁。XX:GCTimeRatio参数设置垃圾回收时间占总时间的比例,0GCTimeRatio相当于设置吞吐量大小;垃圾回收执行时间与应用程序执行时间之比的计算方法为:1/(1+n)例如选项-XX:GCTimeRatio=19,设置垃圾回收时间为总时间的5%--1/(1+19);默认值为1%--1/(1+99),即n=99;看起来准确的最佳临界点确实是ParallelScavenge收集器的配置。不用担心,HotSpot提供了另一个参数XX:+UseAdptiveSizePolicy来帮助我们实现GC自适应调整策略。它会根据当前系统运行收集性能监控信息,并动态调整这些参数以提供最合适的停顿时间或最大吞吐量。开启该参数后,JVM可以动态分配新生代的大小(-Xmn)、Eden区与Survivor区的比例(-XX:SurvivorRation)、晋升到老年代的对象年龄(-XX:PretenureSizeThreshold),等等;在这里插入一个作者的经验:面试的时候有人问我,Eden区和Survivor区的比例可以改变吗?这里的童鞋可以回答:ParallelScavengecollectorsenableXX:+UseAdptiveSizePolicy可以动态分配。这也是ParallelScavenge收集器优于ParNew收集器的重要一点。SerialOld收集器SerialOld是Serial收集器的老版本,同样继承了Serial收集器单线程的特点。工作模型图显示在串行收集器中。ParallelOld垃圾收集器ParallelOld垃圾收集器是ParallelScavenge收集器的老版本,它继承了ParallelNew多线程对的特性,在JDK1.6及之后用于替代旧的SerialOld收集器;参数“-XX:+UseParallelOldGC”:指定使用ParallelOld收集器;工作模型图显示在ParallelNew收集器中。接下来,我将解释G1收集器中的CMS。之前我会理解一个概念,之前可能已经提到过,但是这里理解下面的也不晚。并行(Parallel)是指多个垃圾回收线程并行工作,但此时用户线程仍处于等待状态;如ParNew、ParallelScavenge、ParallelOld;并发(Concurrent)表示用户线程和垃圾回收线程同时执行(但不一定并行,可能交替执行);用户程序继续运行,垃圾回收程序线程运行在另一个CPU上;如CMS、G1(也为并行);CMS收集器并发标记清除收集器也称为并发低暂停收集器或低延迟垃圾收集器;他的目的是:低停顿。HotSpot在JDK1.5推出了第一个真正意义上的并发(Concurrent)收集器;他使用的是“标记清除算法”,所以会产生大量的空间碎片。为了解决这个问题,CMS可以通过配置以下两个参数来解决:-XX:+UseCMSCompactAtFullCollection:参数,强制JVM在FGC完成后对老年代进行压缩,并进行空间碎片整理,但是空间碎片整理阶段也将触发STW。为了减少STW的数量,也可以配置CMS。-XX:+CMSFullGCsBeforeCompaction=n:参数,执行n次FGC后,JVM会在老年代进行空间碎片整理。关联的老年代对象都有标记,速度很快。ConcurrentMarkingPhase使用多个并发标记线程并行执行,与用户线程并发执行。这个过程进行可达性分析,将所有这些可达的对象标记为Inventory对象,非常慢的标记(Remark)因为并发标记时有用户线程在执行,标记结果可能会改变停止所有用户线程,使用多个标记线程在执行parallel,retraversingallchangesduringconcurrentmarking为最终标记标记对象。这个进程的运行时间介于初始标记和并发标记之间。当时的G1收集器G1(Garbage-First)是JDK7-u4推出的商业收集器;比CMS更高级,并行并发,可以充分利用多CPU、多核环境的硬件优势;G1Parallelism和Concurrency的以下特性可以在多CPU和多核环境中充分利用硬件优势;可以并行化,缩短“StopTheWorld”暂停时间;也可以是并发的,允许垃圾回收与用户程序同时进行;分代收集,收集范围包括新生代和老年代,可以独立管理整个GC堆(新生代和老年代),无需与其他收集器协作;可以对不同时期的对象进行不同的处理;虽然保留了分代的概念,但是Java堆的内存布局有很大的不同;整个堆被分成多个大小相等的独立区域(Regions);新生代和老年代不再是物理隔离的,它们都是Region(不需要连续)集合的一部分;结合多种垃圾回收算法,空间整合,无碎片从整体上看,它是基于mark-sorting算法;从本地(两个Region之间)的角度来看,是基于copy算法;这是一个类似于train算法的实现;不会产生内存碎片,有利于长时间运行;可预测的停顿:低停顿同时实现高吞吐G1除了追求低停顿外,还可以建立可预测的停顿时间模型;可以明确指定M毫秒的时间片,垃圾回收花费的时间不要超过N毫秒;例如:当堆大小约为6GB或更大时,可预测的暂停时间可以低于0.5秒;用于替代JDK1.5中的CMS收集器;上面说的Region这个概念肯定不会理解。让我们看看G1的内存模型。G1将Java堆空间划分为若干个大小相同的区域,即regionHumongous是Old的一种特殊类型,在JDK11中专门为大对象放置。G1已被设置为默认的垃圾收集器。G1垃圾回收进程最初标记与GCRoots直接关联的对象,停止所有用户线程,只启动一个初始标记线程。这个过程被快速并发标记以进行全面的可达性分析。启动并发标记线程与用户线程并行执行。这个过程比较长。最后的标记标记了用户线程在并发标记过程中产生的新垃圾。停止所有用户线程,使用多个finalmark线程并行执行。筛选和回收废弃物。这时候也需要停止所有的用户线程,使用多个筛选回收线程并行执行。题外话:我们知道JDK8是目前使用最广泛的版本,而JDK9、10是过渡版本,企业应用效果并不好。JDK11是里程碑版本,相当于现在的JDK8,JDK11默认使用G1收集器,G1的性能在CMS早些年就已经很突出了,官方的性能测试结果也是这样解释的。本文转载自微信公众号“颜琳”,可通过以下二维码关注。转载本文请联系颜琳公众号。
