概述:这里说的经典垃圾收集器,并不是说这些垃圾收集器多么的优秀,因为随着JDK版本的不断更新,新的垃圾收集器越来越多,这些在JDK9及之前使用的垃圾收集器自然就成为了相对经典的版本。说到垃圾收集器,就必须说垃圾收集算法 点击查看垃圾收集算法详解 ,因为垃圾收集算法是收集收集器的方法论,正是因为有了垃圾收集算法,才有了各种各样的垃圾收集器,下面认识下这些经典的垃圾收集器吧。
先附上一张各个垃圾收集器之间的关系图,之间有连线表示可以配合使用,中间有JDK 9标志的表示JDK9开始已经不支持这种搭配(CMS 与 Serial Old配合使用另有原因后面会说明):
一.新生代收集(Minor GC)
1.Serial 收集器
特性:
Serial 采用“标记-复制算法”实现,是最基础、也是历史最悠久的垃圾收集器,曾经是新生代的唯一解决方案,Serial 是单线程运行的,不仅是单线程垃圾回收,而且在进行垃圾回收时会暂停其他所有的所有线程,暂停其他所有线程的这种行为又被称为Stop The World,简称STW,这个名词会在后面的垃圾收集器中经常看到。
使用场景:
Serial 虽然问世十分的早,但依然是一款活跃的垃圾收集器,到目前为止,它依然是客户端虚拟机上新生代默认的垃圾收集器(客户端默认参数:-XX:+UseSerialGC,含义是使用Serial+Serial Old),与其他垃圾收集器相比,Serial具有简单、高效的特点,对于内存资源受限的环境,它是所有收集器额外内存消耗最小的,所以很实用客户端的虚拟机。
与老年代收集器的搭配:
①. Serial 可与 Serial Old配合使用,这是客户端虚拟机默认的垃圾收集组合。虚拟机参数设置:-XX:UseSerialGC。②.Serial 可与 CMS 配合使用,JDK9中取消了该种组合的使用,可见该种组合性能并不是多好。
与该垃圾收集器相关的虚拟机配置参数:
-Xss256k,设置虚拟机栈和本地方法栈大小
-Xmx10m,设置堆最大内存
-Xms10m,设置堆最小内存
-Xmn5m,设置新生代大小
-XX:+UseSerialGC,使用Serial + Serial Old的垃圾收集器组合
-XX:SurvivorRatio=8,新生代中Eden占10份中的比例,默认就是8。
-XX:+PrintGCDetails,告诉虚拟机在发生GC时,打印回收日志(JDK9之前有效)
-XX:MaxTenuringThreshold=15,对象年龄大于该值就会进入老年代,Parallel Scavenge中默认值为15,CMS中默认值为6,G1中默认值为15
-XX:PretenureSizeThreshold=3145728,晋升老年代的对象的大小,大于该值直接进入老年代,这里是3M,该值只能写成以字节为单位的形式。
附上一张在IDEA中设置虚拟机参数截图:
该配置下运行main(main里面就一行无关代码,这里不展示了)方法的结果,如下:
F:\java\bin\java.exe... Heap def new generation total 9216K, used 2794K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 34% used [0x00000000fec00000, 0x00000000feebaa78, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3207K, capacity 4496K, committed 4864K, reserved 1056768K class space used 356K, capacity 388K, committed 512K, reserved 1048576K Process finished with exit code 0
从上面的输出可以看出eden space 8192K,这是8M和我们配置一样,【tenured generation total 10240K】老年代10M也和我们配置一样,没有问题。
附上一张Serial 收集器的导图:
2.ParNew 收集器
特性:
ParNew同样采用标记算法实现,事实上ParNew就是Serial的多线程版本,除了使用多线程对垃圾进行回收外,其余所有场景均与Serial相同(包括Serial支持的配置参数、STW、对象分配回收策略等),但随着JDK9禁用参数-XX:+UseParNewGC,ParNew成为了CMS的新生代解决方案,不再支持单独的参数配置,也成为了第一个退出历史舞台的垃圾收集器,在单核或者伪双核机器中Serial 要更优于ParNew,在双核以上机器中ParNew表现还是优于Serial的。
使用场景:
ParNew在JDK9只后被取消是有原因的,ParNew主要是为了配合CMS使用的,而JDK中新增加的G1,就是为了取代CMS而设计的。JDK9之前,ParNew可以与Serial Old或者CMS收集器配合使用。
与老年代收集器的搭配:
ParNew可以与Serial Old搭配,使用参数-XX:+UseParNewGC(JDK9取消了该参数)设置,ParNew与CMS搭配时,使用参数-XX:+UseConcMarkSweepGC设置(JDK9取消了该参数)。
与该垃圾收集器相关的虚拟机配置参数:
Serial支持的参数ParNew均支持(非收集器设置参数),此外因为ParNew是多线程所以可以设置线程运行的个数
-XX:ParallelGCThreads=3,代表垃圾回收线程最多可以3条同时运行。
-XX:+UseParNewGC,使用ParNew + Serial Old 的垃圾收集器组合(JDK9取消了该参数)。
-XX:+UseConcMarkSweepGC,使用ParNew + CMS的垃圾收集器组合(JDK9取消了该参数)。
下面展示下IDEA下面设置虚拟机参数截图:
可以看到设置的垃圾收集器是ParNew + Serial Old的组合,运行main方法后有如下输出(main里面就一行无关代码,这里不展示了),从输出标红的地方可以看出,已经提示我们,ParNew在未来版本中可能会被废除。
附上一张ParNew的导图:
3.Parallel Scavenge 收集器
特性:
Parallel Scavenge 使用“标记-复制算法”实现,也是一款多线程垃圾收集器,他的很多特性都和ParNew相同,但它也有自己的显著特点,比如这是第一款比较关注应用程序吞吐量(可以看做一定时间内应用程序支持的用户操作次数)的垃圾收集器,与之相对的CMS则是第一款比较关注延时的收集器(延时指STW的时间),此外Parallel Scavenge 还是这三种Minor GC中唯一不能与CMS配合使用的收集器。
使用场景:
Parallel Scavenge可以与Serial Old配合使用,这是在Parallel Old未出现之前的无奈之选。Parallel Old面试以后,Parallel Scavenge + Parallel Old 的组合便成了标准的吞吐量有限的垃圾收集器组合,同时这个组合也是JDK9之前,服务端虚拟机默认的垃圾收集器组合。
与老年代收集器的搭配:
①.Parallel Scavenge + Serial Old,使用参数-XX:+UseParallelGC设置,②Parallel Scavenge + Parallel Old,使用参数-XX:+UseParallelOldGC设置。
与该垃圾收集器相关的虚拟机配置参数:
与Serial相同,除了关于收集器的设置参数,该收集器都是支持的,自然也包含-XX:ParallelGCThreads设置垃圾收集线程个数的参数。此外该收集器还支持一些较关注程序吞吐的参数配置。如下所示:
-XX:MaxGCPauseMillis=200,最大停顿时间(STW),200是毫秒,也是默认值,该值不可一味追求小,过小会提前或者频繁触发GC,这个参数也是通过改变回收集的大小来实现的。
-XX:GCTimeRatio=99,该值代表垃圾收集时间占了1/(1+99)。如果是19则表示垃圾收集占了1/(1+19)的时间。
-XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开开关后,虚拟机会根据用户设置的上限两个参数来动态调整-Xmx、-Xms、-Xmn、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等参数,这也是该收集器区别于ParNew的重要特征。
来测试下-XX:+UseAdaptiveSizePolicy这个看起来很牛气的参数,附上一张IDEA的参数截图:
截图中可以看出是没有设置堆、栈、Eden比例等参数的。看下输出看看使用Parallel Scavenge + Parallel Old 的组合后这些参数变成了了什么,从下图可以看出新生代大约是55M,老年代大约是127M左右,这说明了虚拟机为我们设置了它认为的合理参数。
下面我们写个程序验证下这些参数到底会不会动态扩展,还是说设置完以后便不动了,代码如下:
public class TestSerial { public static void main(String[] args){ List<String> list = new ArrayList<>(); int n = 0; while(true){ n++; String str = new String("123"); list.add(str); if(n%10000==0){ System.gc(); } } } }
根据上方的代码,如果堆空间不变化,那么迟早程序会OOM,上面的运行方法展示的堆空间是130048k,我们看下下面的运行截图
从上面的截图中我们可以看到,老年代在不断的被填充,老年代的使用这么增长下去,理论上一会就会被填满,然后报OOM,实际上的运行结果却是下面这样,下图中第一处标红老年代还是原来的大小,但是再一次增长后,老年代内存就变大了一部分,说明虚拟机感觉老年代不够用,对老年代空间进行了动态调整,事实上只要程序继续运行下去,老年代值就会不断增长下去,直至到达物理内存的瓶颈,这也证明了-XX:+UseAdaptiveSizePolicy这个动态调整虚拟机参数的开关确实是好使的。
附上一张Parallel Scavenge的导图:
二.老年代收集(Major GC)
1.Serial Old 收集器
特性:
Serial Old 采用“标记-整理算法”实现,从名字上可以看出该收集器与新生代收集器Serial很像,事实上该收集器与Serial确实是类似的,它是老年代版本的Serial,特点上也是与Serial类似,都是单线程工作,该收集器运行时也是需要暂停其他线程的(STW)。
使用场景:
①.Serial + Serial Old 到目前为止仍是客户端模式下虚拟机的默认垃圾收集器,②.Parallel Scavenge+ Serial Old 这个组合是在JDK5及之前服务端模式下默认的垃圾收集器,JDK6提供了Parallel Old后服务端模式下变成了Parallel Scavenge + Parallel Old收集器组合。③.虽说随着JDK版本的不断更新,Serial Old不在作为服务端模式下的老年代解决方案,但是它仍是会在使用CMS时作为一种解决CMS发生Concurrent Mode Failure的解决方案,CMS发生Concurrent Mode Failure表示多线程回收垃圾以及不能满足用户线程的运行下新对象的分配,这是就会启用Serial Old对老年代进行单线程回收。
与新生代收集器的搭配:
①Serial + Serial Old ,使用参数-XX:+UseSerialGC设置,也是客户端虚拟机默认参数。
②Parallel Scavenge + Serial Old,使用参数-XX:+UseParallelGC设置,这是JDK5及之前服务端默认参数。
③ParNew + CMS + Serial Old ,使用参数-XX:+UseConcMarkSweepGC设置,这是JDK9之后的搭配场景(JDK取消了ParNew的其他搭配,将其作为CMS的新生代解决方案,不在单独存在)
与该垃圾收集器相关的虚拟机配置参数:
除了自己独特的几种搭配不同新生代收集器的配置参数,其他可以说与Serial的配置参数基本一样,这里只展示与Serial 不一样的配置参数:
-XX:+UseParallelGC,使用Parallel Scavenge + Serial Old收集器组合。
-XX:+UseConcMarkSweepGC,使用ParNew + CMS + Serial Old收集器组合(JDK9中参数)。
这里只能演示下使用-XX:+UseParallelGC该参数的场景了,IDEA配置参数如下图。
正常运行main方法后输出如下,可见参数配置时生效状态
附上一张Serial Old的导图:
2.Parallel Old 收集器
特性:
Parallel Old 采用“标记-整理算法”实现,从名字上可以看出该收集器与新生代的Parallel Scavenge收集器类似,事实上该收集器就是Parallel Scavenge的老年代版本,功能上也是类似,只不过该收集器的收集区域是老年代而已,此外Parallel Scavenge + Parallel Old,还是第一种比较关注吞吐量的收集器组合。
使用场景:
Parallel Old只能与Parallel Scavenge收集器配合使用,该收集器开发出来就是为了搭配Parallel Scavenge使用的,同时Parallel Scavenge + Parallel Old 的收集器组合还是服务端模式下默认的垃圾收集器组合(JDK9之前,JDK9默认是G1)。
与新生代收集器的搭配:
只能与Parallel Scavenge配合使用,值的注意的是-XX:+UseParallelGC,配置使用的是Parallel Scavenge + Serial Old组合。
与该垃圾收集器相关的虚拟机配置参数:
与Parallel Scavenge参数配置相同,这里不重复介绍了。
附上一张导图:
3.CMS(ConcurrentMarkSweep) 收集器
特性:
CMS 收集器是一款基于“标记-清除算法”实现的收集器,从它的名字可以看出,它是并发的,这个并发指的是可以与用户线程并行执行,之前的老年代收集器无论是Serial
Old,还是Parallel Old都是在收集对象是都是需要全程暂停用户线程的,CMS做到了与用户线程并行,在垃圾收集时基本不暂停用户线程(暂停时间很短,基本忽略不计),因为和用户线程并行因此就必须考虑一种情况,如果在并发收集期间收集到的内存并不足以支撑用户新对象的分配的情况,这时就会发生Concurrent Mode Failure,虚拟机会暂停CMS收集动作,转而使用它的担保策略Serial Old暂停用户线程,进行单线程收集。它也是第一款比较成功的低延时收集器的尝试,不过有意思的是虽然CMS是第一款追求响应时间小的收集器,但是它并不能支持该参数-XX:MaxGCPauseMillis,在虚拟机配置中设置该参数是不会起作用的。
使用场景:
JDK9之前CMS可以与Serial、ParNew配合使用,最常用的使用组合还是与PawNew配合使用,因为ParNew是多线程收集,CMS同样是支持并发的,但是在JDK9之后,ParNew不在作为单独的收集器提供服务,而是被作为CMS的新生代解决方案,同时其他与ParNew相关的参数也失效了,ParNew也是第一款退出历史舞台的收集器,CMS相对于Parallel Old的多线程收集,优点在与能与用户线程并行,那为什么不使用CMS作为服务端默认的垃圾收集器呢,因为CMS对处理器资源非常敏感,当在4核以下的服务器中使用CMS作为收集器,效率并不是很高。
与新生代收集器的搭配:
①可以与Serial 搭配使用,不过不是一种合适的选择,当老年代选择CMS时,服务器的处理器应该至少在4核以上,此时选用Serial会降低性能,但是这是一种可行的搭配策略。②可以与ParNew搭配使用,ParNew支持多线程收集,相对于Serial不会有资源浪费,能获得更好的回收效率。这也是G1出现之前,多核处理器建议的垃圾收集组合。
CMS 收集器的收集过程:
CMS 回收对象的过程,相对于之前介绍的垃圾收集器,都更为复杂,因此介绍下CMS回收对象主要的4个步骤,①初始标记、②并发标记、③重新标记、④并发清除。
①初始标记:暂停用户线程,时间很短,仅仅标记可以与GC Roots直接关联的对象。
②并发标记:不暂停用户线程,时间较长,从GC Roots直接关联的对象开始向下遍历对象图。
③重新标记:暂停用户线程,时间很短(长于初始标记远短于并发标记),因用户线程时并行,此阶段主要是标记因用户线程并行而造成的标记变更的对象。
④并发清除:不暂停用户线程,时间中等,删除掉被判定为垃圾的对象,值得注意的是被标记的不一定被清除,“标记-清除算法”中声明过,可以标记垃圾对象,然后清除,当存活对象较少时也可以标记存活对象,清除未被标记的对象,这就是CMS的大致收集过程。
附上一张CMS运行的示意图,这个图可以很直观看出CMS运行的过程:
与该垃圾收集器相关的虚拟机配置参数:
正常的栈、堆、晋升老年代大小、Eden比例、并行线程数CMS同样都是支持的(参考Serial、ParNew),这里只介绍CMS独特的配置参数。
-XX:+UseConcMarkSweep,使用ParNew + CMS的组合,JDK9之前多核(4核以上)服务器的首选。
-XX:CMSInitiatingOccupancyFraction=92,这是个百分值,默认92,设置堆空间使用率达到多少时触发CMS工作,该值不宜太高,过高容易造成并行期间用户线程产生的对象无处分配,导致Concurrent Mode Failure。
-XX:+useCMSCompactAtFullCollection,这是一个开关参数,默认就是开启的,打开时在触发Full GC时会先对堆空间进行整理,因为CMS使用“标记-清除算法”实现,堆中会有空间碎片的问题,所以也有一种说法说CMS是一种“标记-清除算法”与“标记-整理算法”的和稀泥式实现(这里的堆空间整理是依赖Serial Old实现的,所以不能算是CMS,CMS还是基于“标记-清除算法”的)。
下面测试下-XX:+UseConcMarkSweep、-XX:CMSInitiatingOccupancyFraction=50,这个配置,另外一个默认开启,虚拟机配置如图:
从上图可以看出我们设置的是堆空间100M,使用比例达到50%触发CMS,下面看下输出日志,从日志中可以看出CMS基本就是在堆内存占用即将达到50%时触发,不可能保证百分百精确,说明这个参数配置是有用的。
总结CMS的优缺点:
优点:①CMS是支持用户线程并行的,这是一个优于之前收集器的很大的点。②CMS是一款成功的低延时垃圾收集器,尽管不支持延时的配置参数,并不影响他的优秀。③CMS在多核处理器的服务器上表现会优于其他老年代收集器。
缺点:①CMS在多核服务器上的表现是优点那么在4核以下的处理器中就是致命的缺点了,CMS默认开启的回收线程数是(处理器核心数+3)/4,当处理器核心数在4以下时,垃圾收集线程占用的cpu资源就会在25以上,这就会导致应用程序的吞吐量下降。②因为CMS是基于“标记-清除算法”实现的,所以CMS收集对象后肯定会有空间碎片,因此CMS不得不与Serial Old搭配使用,在发生Concurrent Mode Failure时使用Serial Old对堆空间进行整理,从而消除CMS收集后留下的空间碎片。③因为CMS与用户线程是并行的,所以如果在并行期间如果产生过多“浮动垃圾”也会导致Concurrent Mode Failure。
最后附上一张CMS的导图:
三.混合代收集(Mixed GC)
1.Garbage First 收集器(G1)
什么是G1 收集器?
G1是一款基于“标记-整理算法”的收集器,同时局部还有“标记-复制算法存在”,G1是垃圾收集器发展史上里程碑式的成果,它开创了面向局部收集的思路与基于Region的内存布局形式,G1被设计的初衷是用来取代JDK5发布的CMS收集器,因此在JDK9之后G1也被设置成了服务端模式下默认的垃圾收集器取代了Parallel Scavenge + Parallel Od的组合,而CMS在JDK9只有则沦落到称为不被推荐的收集器,而且在未来的版本中CMS可能会被抛弃。既然G1设计之初就是作为CMS的替代品存在,那么作为第一款成熟的低延时收集器CMS具备的低延时功能,这个特点G1肯定也是必须具备的,但是与CMS不同的是G1支持-XX:MaxGCPauseMillis这个参数配置,可根据这个参数来控制想要达到的延时时间。此外在G1中堆里不在明确的划分出新生代、老年代、Eden、Survivor等区域,而是以Region为基本单位,堆中划分出了很多的Region空间,每个Region都可以充当新生代、老年代、Eden、Survivor等区域,当然不同的区域也会有不同的回收策略,因此G1不再是单独面向新生代或者单独面向老年代组建回收集,而是会根据各个Region空间的回收价值去去动态组建回收集,回收内存的依据不再是根据对象所处的分代,而是各个Region的回收收益价值,然后组建出一个可能既含有充当了新生代Region又含有充当了老年代Region的回收集,着也就是G1的Mixed 模式,俗称混合收集。
使用场景:
G1作为一款划时代的收集器,它可以独立的完成整堆的垃圾收集工作,不需要与其他垃圾收集器配合,JDK9以后G1作为服务端模式下默认的垃圾收集器登上历史的舞台,取代了之前的Parallel Scavene + Parallel Old收集器组合。
G1如何解决跨代引用问题:
我们都知道,在传统的被划分为新生代、老年代的堆内存模型中,新生代中会有一块记忆集来维护与老年代中相关的引用,这样就避免了回收新生代时因为跨代引用而扫描整个堆。那么G1是怎么解决这个问题的呢,在G1中每个Region中都维护了一个记忆集,这个记忆集在存储结构上本质是一个哈希表,就像我们最常用的HashMap一样,数据结构都是哈希表,哈希表的典型结构就是键值对了。在G1的记忆集中,key是其他Region的起始地址,value则是一个集合存储了卡表的索引号,这记录了“谁指向我”与“我指向了谁”,G1就是通过这种记忆集来实现了跨代引用,因为G1会把堆划分为多个Region且每个Region都会维自己的记忆集,因此G1收集器要比其他传统的收集器有着更高的内存占用,根据经验G1要耗费堆中10%-20%的内存来维持收集器的工作。
G1如何实现用户线程与收集线程的并行:
用户线程与收集线程并行最先应该考虑的问题就是,不能因为用户线程的并行而打破原本的对象图结构,这样搞会导致对象的标记失去意义,CMS采用的是增量更算法实现,在并发标记结束后,的下一阶段重新标记中将并行期间产生的对象增量更新进入对象图,而G1则是使用的原始快照算法来实现的,G1为每个Region设计了连个名为TAMS的指针,使用这两个指针把Region中的一部分区域划分出来,咱们用于程序并行期间的新对象的分配,这样也不会影响到并行期间原始对象图的结构。既然都是拥有与用户线程的并行能力,那与CMS类似的是,当CMS并行期间预留的内存不足以新对象的分配时会Concurrent Mode Failure,从而触发Full GC(实际上是Serial Old),G1也会在这种情况下触发Full GC,从产生长时间的STW。
G1如何建立可靠的停顿时间模型:
CMS虽然设计初也是作为一款低停顿时间的收集器,但是CMS并不支持-XX:MaxGCPauseMillis参数,真正停顿多少时间还是看虚拟机自身的运行情况,与CMS不同的是G1则支持该参数的配置,那G1是怎么实现该机制的呢?G1的停顿时间模型是以衰减均值为理论基础来实现的,在垃圾收集过程中,G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,然后根据这些信息分析得出平均值、标准偏差、衰减平均值等,衰减平均值更能代表“最近的”平均状态,因此也更能体现Region的回收价值,然后根据这些信息和用户期望的回收时间来动态组建回收集,而不是想其他收集器那样去把垃圾全部回收掉。
G1的回收对象的主要过程:
G1作为CMS的替代者,自然不会对对象的处理完全相同,不然也达不到改进的效果,看下G1回收内存的四个主要步骤
①初始标记:暂停用户线程、时间很短,该阶段仅仅标记与GC Roots直接关联的对象,且调整TAMS指针的值,方便在下一阶段存储用户线程需要分配的对象。
②并发标记:不暂停用户线程、时间较长,从GC Root直接关联的对象开始遍历对象图,同时通过STAB记录下有引用变动的对象。
③最终标记:暂停用户线程,时间很短,处理下STAB记录下有引用变动的对象。
④筛选回收:CMS的第四步叫并发回收,这里叫筛选回收很明显它需要暂停用户线程、时间较短,G1建立的可靠的时间停顿模型也是在这一步完成的,这一步负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,然后根据用户期望的停顿时间来制定回收计划,动态组建回收集,然后把决定回收的那部分的Region的存活对象复制到空的Region中,在清理掉这个旧的Region的空间(所以说G1局部也存在“标记-复制算法”)。
从G1回收对象的四个主要步骤可以看出,除了并发标记外,其他步骤都是需要暂停用户线程的,换言之,他并非纯粹的追求低延时,官方给他设定的目标是在延时可控的情况下获得尽可能高的吞吐量,所以才能担当的起“全功能收集器”的重任与期望。
附上一张G1收集器工作的流程图
G1的停顿时间如何设置:
毫无疑问,可以由用户指定停顿时间是一个很强大的一个功能,但是这个时间也一定要合理设置,合理的设置停顿时间可以实现吞吐量与低延时的一个最佳平衡,G1要要通过停顿时间进行复制对象的(主要是干这个事),所以这个值并不是越低越好,设置的很低只会让组建的回收集更小,从而频繁触发GC,这个值在G1中默认是200毫秒,一般100-300毫秒之间都是正常的设置范围,这个值的降低势必会影响到回收集的大小,因此很大程度上都是用空间换时间,所以需要选取一个适中的值,不宜过大过小,从G1开始不再像之前的垃圾收集器一样,一次回收都是关注整个新生代、老年代,G1开创了只回收部分区域的先河,每次只需要保证回收到的区域足够用户使用就能保证程序的运行,这样也就实现了程序边运行,收集器边收集内存,应用程序不在需要程序等待收集器完全完成工作才能继续工作了。所以说G1是收集器发展的一个里程碑。
对比CMS与G1收集器:
既然设计G1的初衷是为了取代CMS,那我们肯定要分析下G1到底在哪些方面胜过了CMS从而能让JDK9开始G1成为了服务端模式下虚拟机的默认收集器。
相同点:两者都是关注低延时的收集器,主要回收流程都是经历4个部分。
CMS的优点:①相比于运行G1收集器,运行CMS内存占用率更低(随着物理机的发展,这个已经不能算是G1的缺点了)。②CMS可认为是并行执行,因为最为耗时的并发标记、并发清除都是可以与用户线程并行的,而G1只有并发标记是并行的,G1里最重要的筛选回收是需要暂停用户线程的,G1是在筛选回收阶段,才能确立回收目标。
G1的优点:①CMS使用“标记-清除算法”实现,会有空间碎片产生,而G1使用“标记-整理算法”不会有碎片,即使局部有“标记-复制算法”同样不会产生空间碎片。②G1可以设置最大停顿时间,达到一个可控的时间模型,CMS不支持。③G1可以动态组建回收集,无需整堆回收,而CMS则是整堆回收(JDK9之后),G1的动态组建回收集,用户体验会更好。
总结以上两者优缺点:在小内存的服务器上,CMS表现更优秀一些,在大内存(8G以上),核心数更多的服务器上使用G1则能或得更好的回收效果。
与该垃圾收集器相关的虚拟机配置参数:
-Xss256k,设置虚拟机栈大小
-Xmx10m,设置堆最大内存
-Xms10m,设置堆最小内存
-XX:ConcGCThreads=2,并发标记阶段使用线程数,可适当高一点。
-XX:G1NewSizePercen=5,新生代占用堆最小值,默认5%,该参数在jdk9之前默认不开启,如需使用此参数还需要增加其他配置才可
-XX:G1MaxNewSizePercent=60,新生代占用最大值,默认60%,该参数在jdk9之前默认不开启,如需使用此参数还需要增加其他配置才可
-XX:MetaSpaceSize=10m,方法区最小值(元空间)
-XX:MaxMetaSpaceSize=10m,方法区最大子,默认-1,没有最大
-XX:InitiatingHeapOccupancyPercent=92,触发G1的内存使用率
-XX:SurvivorRatio=8,Eden区域所占10份中的比例
-XX:ParallelGCThreads=2,收集线程的个数一般与服务器核心数相同
-XX:MaxTenuringThreshold=15,设置进入老年代对象的年龄。
-XX:+UseG1GC,使用G1收集器
-XX:MaxGCPauseMillis=200,设置最大停顿时间,默认就是200毫秒
-XX:G1HeapRegionSize=2,设置每个Region的大小,该值取值范围是1-32,且必须是2的n次幂,当对象的值达到Region设置的值的一半时,被设为大对象会存入humongous区域,更大的对象存储在N个连续的Humongous Region中,G1中的大多数行为都把Humongous Region看做老年代的一部分。
四.全文总结
这篇文章里,介绍了三种不同的新生代垃圾收集器Serial 、ParNew、Parallel Scavenge,又介绍了三种不同的老年代收集器Serial Old、Parallel Old、CMS,还有一种混合收集器Mixed GC。收集器的使用要根据服务器的配置才能决定,假设是服务器是大内存、处理器核心数足够高的情况下。G1优于CMS优于Parallel Scavenge + Parallel Old优于Serial + Serial Old,基本会有这种情况。但真正在实际环境中还是得根据不同服务器选择不同的收集方案,以便应用程序可以达到最好的用户体验。