垃圾收集器是内存回收算法的具体实现。下图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。
下面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。
一个单线程的收集器,但它的单线程的意义不仅仅是说明它只会使用一个CPU或一条线程去完成垃圾回收,更重要的是在垃圾收集时,必须暂停其他所有的工作线程(“Stop the World”),直到收集结束。
Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单 线程收集效率。
优势:简单而高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,可以获得最高的单线程收集效率。
对于运行在Client模式下的虚拟机来说是一个很好的选择。
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾回收以外,其余都与Serial收集器相同。
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中一个与性能无关的重要原因是,除了Serial收集器外,目前只有ParNew收集器能与CMS收集器(一款并发的老年代收集器)配合工作。
Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。它是 以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。
吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法。主要使用在Client模式下的虚拟机。
如果在Server模式下,它主要有两大用途:一是在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用,另一个就是作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure的时候使用。
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。
CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或 者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会 产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。
大致分为4个步骤:
①.初始标记(CMS initial mark):标记出GC ROOTS能直接关联到的对象,速度很快
②.并发标记(CMS concurrenr mark):进行GC ROOTS 根搜索算法阶段,会判定对象是否存活
③.重新标记(CMS remark):修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶 段的停顿时间会比初始标记阶段稍长,但比并发标记阶段要短。
④.并发清除(CMS concurrent sweep)
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,器主要有三个显著缺点:
CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。
CMS 收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数 -XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。
最后一个缺点,CMS是基于“标记 -清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。
G1(Garbage First)收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。还有一个特点之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代,老年代)。
使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小不等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
在G1收集器中,Region之间的对象引用以及其他收集器的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不会对全堆扫描也不会有遗漏。
G1收集器的运作步骤:
初始标记阶段只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。
并发标记阶段:从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这个阶段耗时较长,但可与用户程序并发执行。
最终标记阶段:为了修正在并发标记阶段因用户程序运作而导致标记变化的那一部分标记记录,虚拟机将这部分记录记录在Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs里的数据合并到Remembered Set中,这阶段需要停顿线程,但可与用户程序并发执行。
筛选回收阶段:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来指定回收计划。这个阶段可与用户程序并发执行,因为只回收一部分Region,时间是用户可控制的。
原文:http://blog.csdn.net/bruce_6/article/details/38553143