Java的垃圾回收器

    本文只涉及垃圾回收器,对垃圾回收算法感兴趣请戳垃圾回收算法。

    垃圾回收器的实现,跟厂商有很大关系。这里主要讨论基于JDK1.7 Update14之后的HotSpot虚拟机。新生代收集器使用的收集器:Serial、PraNew、Parallel Scaveng;老年代收集器使用的收集器:Serial Old、Parallel Old、CMS;以及G1收集器。

Java的垃圾回收器_第1张图片 图像来自互联网(侵删)

    图中的垃圾收集器之间存在连线,说明它们可以搭配使用。

    实际上,目前没有任何一款收集器是最好的,只能说针对不同应用场景选择更合适的收集器。

1、Serial收集器(复制算法)

    Serial收集器是最基本、最悠久的收集器。这是新生代单线程收集器。这里的“单线程”的意义,既是指它只会使用一个CPU和一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停所有用户线程。

    虽然Serial收集器单线程的特点造成用户体验很差,但是它也有巨大的优点点,简单而高效。尤其对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程手机效率。目前Serial收集器仍是桌面级Client模式下虚拟机的默认新生代收集器。

    简而言之,缺点是会造成停顿,优点是简单高效,适合桌面级Client模式。

2、ParNew收集器(复制算法) 

    ParNew收集器实际上就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop the World、对象分配规则、回收策略等都与Serial收集器完全一样。

    ParNew收集器是许多Server模式下的虚拟机中首选的新生代收集器,其中一个重要原因是处理Serial收集器外,目前只有ParNew收集器可以和老年代的CMS收集器配合。

    ParNew在单CPU情况下,由于存在线程切换的开销,哪怕在两个CPU的情况下效率都有可能比Serial更低;但是随着CPU数量的增加,ParNew对GC时系统资源的有效利用也回来越好。

3、Parallel Scavenge收集器(复制算法)

    Parallel Scavenge收集器是新生代多线程收集器。它追求的是达到可控的吞吐量。所谓的吞吐量就是CPU用于运行用户代码的时间,与CPU总消耗时间的壁纸,即吞吐量=运行用户代码时间/(运行用户代码时间+GC线程时间)。

    停顿时间越短就越适合需要与用户交互的程序,而高吞吐量则可以高效率利用CPU时间,适合在后台运算不需要太多交互的任务。

    参数解析

    1)Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGcPauseMillis,以及直接设置吞吐量大小的-XX:GCTimeRatio。

    -XX:MaxGcPauseMillis参数允许一个大于0的毫秒数,收集器尽可能保证内存回收时间不超过设定值。但是这个时间的缩短是以牺牲吞吐量和新生代空间来换取的,过度缩小会导致垃圾收集更频繁,停顿时间减少,但吞吐量也会降低。

    -XX:GCTimeRatio参数的值应该是一个大于0小于100的整数,也就是运行用户代码时间和垃圾收集时间的比例。比如设置为19,表示运行用户代码时间:GC时间=19,所以吞吐量是19/(19+1)。参数默认值是99,也就是99%的吞吐量。

    2)收集器还有 “GC自适应调节策略”的开关参数-XX:+UseAdaptiveSizePolicy。参数打开后,不需要指定新生代大小(-Xmn)、Eden与Survivor比例(-XX:SurvivorRatio)、晋升老年代的对象大小(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统的允许情况、收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。

    如果用户对收集器运作原理不太了解,手工优化存在困难,可以使用GC自适应调节策略,只需要去关注基本的内存数据(如-Xmx设置最大堆),然后用MaxGcPauseMillis或GCTimeRatio给虚拟机设立优化目标。

4、Serial Old收集器(标记-整理算法)

    Serial收集器的老年代版本,也是单线程收集器,使用标记-整理算法。

    这个收集器主要也是给Client模式下的虚拟机使用。

    如果在Server模式下,还有两大用途:一种是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配;一种是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

5、Parallel Old收集器(标记-整理算法)

    Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

    在吞吐量优先的场合,优先考虑Parallel Scavenge收集器+ Parallel Old收集器的组合。

6、CMS收集器(标记-清除算法)

    CMS(Concurrent Mark Sweep)收集器是一款以获取最短回收停顿时间为目标的收集器。目前很大一部分应用集中在互联网站或者B/S系统发服务器上,这类应用尤其重视服务的相应速度,希望系统停顿时间最短。

    CMS收集器的运作过程分为4步:

1)初始标记:需要“Stop the World”,用于标记GC Roots能直接关联的对象,速度很快;

2)并发标记:不需要“Stop the World”,进行GC RootsTracing的过程;

3)重新标记;需要“Stop the World”,修正并发标记期间因用户继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间略长于初始标记,但远远短于并发标记的时间;

4)并发清除:不需要“Stop the World”。

    由于整个过程中耗时最长的并发标记和并发清除过程,收集器线程都可以与用户线程以前工作,所以,总体来说,CMS收集器的内存回收过程是与用户线程以前并发执行的。

   CMS收集器的缺点

1)CMS对CPU资源非常敏感:

    在并发阶段,CMS虽然不会导致用户线程停顿,但是还会因为占用了部分CPU资源导致应用程序变慢,总吞吐量降低。CMS默认启动的线程数是(CPU线程数+3)/4。

2)CMS无法处理浮动垃圾,可能出现“Concurrent Mode Failure”而导致另一次Full GC:

    浮动垃圾,指的是并发清理阶段产生的垃圾。因为并发清理阶段用户程序也在运行,产生的垃圾在标记过程之后,所以本次清理过程不会被清理,并且CMS还必须预留一部分空间提供给并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会触发“Concurrent Mode Failure”,虚拟机将启动后备预案,临时启用Serial Old收集器,但停顿时间会明显增加。

    CMS提供了参数-XXCMSInitiatingOccupancyFraction来控制触发CMS的内存使用占比,设置太低会导致CMS触发过于频繁,设置太高则很容易出现大量的“Concurrent Mode Failure”。

3)基于标记-清除算法,收集结束会产生大量空间碎片:

    为解决这个问题,CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数,默认打开。这个参数用于在CMS快要进行FullGC时开启内存碎片的合并整理过程,内存的整理过程无法并发,虽然减少了空间碎片,但是也增加了停顿时间。

    CMS还提供了参数-XX:CMSFullGCsBeforeCompaction,这个参数用于设置执行多少次不压缩的FullGC后,执行一次带碎片整理的FullGC。默认是0,表示每次Full GC都需要进行碎片整理。

7、G1收集器

    G1收集器是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

    在G1中分代概念仍然保留。虽然G1不需要和其他收集器配合,可以独立管理GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。

    G1将内存分成多个大小相等的独立区域,虽然还保留着新生代和老年代的概念,但是新生代和老年代不再是物理隔离的,它们都是一部分Region(不需要连续)的集合。

    G1收集器的优点:

1)空间整合

    G1从整体看是基于标记-整理算法实现的收集器,从局部看是基于复制算法。这两种算法都意味着G1运行期间不会产生大量内存空间碎片。

2)可预测的停顿

    降低停顿时间是G1和CMS共同关注的,但G1能建立可预测的停顿时间模型,让使用者明确指定在一个长度为M毫秒的时间片段内,GC的时间不得超过N毫秒。

3)有计划的垃圾回收:

    G1可以有计划的在Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆的价值大小(回收所获得的空间大小,以及回收所需要的时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这就是Garbage-First的由来。

你可能感兴趣的:(Java虚拟机)