详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt378java
这里介绍4个垃圾收集器,若是进行了错误的选择将会大大的影响程序的性能。web
时至今日,仍然有两个事情困扰着开发人员:垃圾收集(GC)和了解异性(程序猿的悲鸣),后者我确实不太了解,由于我被前者搞的无暇顾及怎么了解异性,特别是当知道在JAVA8中对这一区域有了很大的改进和提高还有移除了PermGen和以一些新的使人兴奋的调优。算法
当咱们谈到垃圾回收时,咱们绝大多数都知道利用它的概念在咱们平常的编程中。可是,当问题出现时,会发现不少是咱们不知道的。JVM 最大误区之一就是它只有一个垃圾回收器,其实是它提供了四个不一样的收集器,每一个都有其自身独特的优点和劣势。垃圾收集器不是自动选择的,这取决我的以及吞吐量和应用程序的差别。编程
这些垃圾收集的广泛存在的共同点是他们都把堆分隔成不一样的片断来管理,好比在age-old区中的大多数对象应该被快速的回收。这些都是老生常谈的事,咱们直接进入主题来看一下各个收集器的不一样以及他们的优缺点。数组
Serial收集器是JAVA虚拟机中最基本、历史最悠久的收集器,在JDK 1.3.1以前是JAVA虚拟机新生代收集的惟一选择。Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不只仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工做,更重要的是在它进行垃圾收集时,必须暂停其余全部的工做线程,直到它收集结束。服务器
Serial收集器到JDK1.7为止,它依然是JAVA虚拟机运行在Client模式下的默认新生代收集器。它也有着优于其余收集器的地方:简单而高效(与其余收集器的单线程比),对于限定单个CPU的环境来讲,Serial收集器因为没有线程交互的开销,专心作垃圾收集天然能够得到最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存通常来讲不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间彻底能够控制在几十毫秒最多一百多毫秒之内,只要不是频繁发生,这点停顿是能够接受的。因此,Serial收集器对于运行在Client模式下的虚拟机来讲是一个很好的选择。多线程
PS:开启Serial收集器的方式 -XX:+UseSerialGC并发
如:Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails性能
-XX:+UseSerialGC的是Serial收集器,Xms30m -Xmx30m 指定了JAVA虚拟机的固定大小为30M,-Xmn10m 指JAVA新生代的空间为10M。优化
这是 JVM 的缺省收集器。就像它的名字,其最大的优势是使用多个线程来经过扫描并压缩堆。串行收集器在GC时会中止其余全部工做线程(stop-the-world),CPU利用率是最高的,因此适用于要求高吞吐量(throughput)的应用,但停顿时间(pause time)会比较长,因此对web应用来讲就不适合,由于这意味着用户等待时间会加长。而并行收集器能够理解是多线程串行收集,在串行收集基础上采用多线程方式进行GC,很好的弥补了串行收集的不足,能够大幅缩短停顿时间(以下图表示的停顿时长高度,并发比并行要短),所以对于空间不大的区域(如young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。
图1.Serial收集器与Parallel/ Throughput(并行)收集器的比较
CMS(Concurrent Mark Sweep)收集器是基于“标记-清除”算法实现的,它使用多线程的算法去扫描堆(标记)并对发现的未使用的对象进行回收(清除)。整个过程分为6个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
并发预清理(CMS-concurrent-preclean)
从新标记(CMS remark)
并发清除(CMS concurrent sweep)
并发重置(CMS-concurrent-reset)
其中初始标记、从新标记这两个步骤仍然须要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而从新标记阶段则是为了修正并发标记期间,因用户程序继续运做而致使标记产生变更的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但远比并发标记的时间短。其余动做都是并发的。
须要注意的是,CMS收集器没法处理浮动垃圾(Floating Garbage),可能出现“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: CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。
该算法与并行收集器的另外一个缺点是吞吐量的它使用更多的 CPU,为了使应用程序提供更好的体验,经过使用多个线程来执行扫描和收集。这种状况长时间的运行会使应用程序停顿下来,可使用提升空间来换取高效的运行。可是,这种算法的使用不是默认的。您必须指定 XX: + USeParNewGC来使用它。若是你能够提供更多的CPU资源的话以免应用程序暂停,那么你可使用CMS收集器。假设你的堆的大小小于 4 Gb你必须分配大于 4 GB的资源。
G1垃圾收集器在JDK7 update 4以后对大于4G的堆有了更好的支持,G1是一个针对多处理器大容量内存的服务器端的垃圾收集器,其目标是在实现高吞吐量的同时,尽量的知足垃圾收集暂停时间的要求。G1在执行一些Java堆空间中的全区域操做(如:全局标记)时是和应用程序线程并发进行的,所以减小了Java堆空间的中断比例。(译者注:可简单理解为减小了Stop-the-World的时间比例)。
它与前面的CMS收集器相比有两个显著的改进:一是G1收集器是基于“标记-整理”算法实现的收集器,也就是说它不会产生空间碎片,这对于长时间运行的应用系统来讲很是重要。二是它能够很是精确地控制停顿,既能让使用者明确指定在一个长度为M毫秒的时间片断内,消耗在垃圾收集上的时间不得超过N毫秒,具有了一些实时Java(RTSJ)的垃圾收集器的特征。
首先将Java堆空间划分为一些大小相等的区域(region),每一个区域都是虚拟机中的一段连续内存空间。G1经过执行并发的全局标记来肯定整个Java堆空间中存活的对象。标记阶段完成后,G1就知道哪些区域基本上是空闲的。在回收内存时优先回收这些区域,这样一般都会回收至关数量的内存。这就是为何它叫作Garbage-First的缘由。顾名思义G1关注某些区域的回收和整理,这些区域中的对象颇有可能被彻底回收。并且G1使用了一个暂停时间预测模型使得暂停时间控制在用户指定的暂停时间内,并根据用户指定的暂停时间来选择合适的区域回收内存。
G1肯定了可回收的区域后就是筛选回收(evacuation)阶段了。在此阶段将对象从一个或多个区域复制到单一区域,同时整理和释放内存。该阶段是在多个处理器上多个线程并行进行的,所以减小了暂停时间并提升了吞吐量。G1在每一次的垃圾收集过程当中都不断地减小碎片,并可以将暂停时间控制在必定范围内。这些已是之前的垃圾收集器没法完成的了。好比:CMS收集器并不作内存整理。ParallelOld收集器只是对整个Java堆空间作整理,这样致使至关长的暂停时间。
在java8 udpate 20中对G1收集器采用了字符串重复消除技术(String deduplication),以前字符串以及内部的char[]数组大量消耗了内存空间,在新的G1垃圾收集器中,将会对内存中重复的字符串进行优化,使他们指向同一个字符数组,以免相同的字符串出现而使堆处理效率低下,你可使用 -XX:+UseStringDeduplicationJVM参数来开启。
在 Java 8 最大的变化之一删除了在堆中为类的元数据、内部字符串和静态变量分配 permgen空间的部分。过去若是加载大量的类到内存中常常会出现内存溢出异常,而且开发人员须要在这个方面作大量的工做,因此若是这段经过JVM来管理了将是一个不错误的优化。
每一个垃圾收集器都有不一样的配置参数,能够经过不一样的参数来提高性能和下降吞吐量。这些都取决于你的应用需求,不一样的对收集方式、可忍受的停顿时间、内存的大小都不同,因此要根据自身的需求来定制不一样的配置参数。