垃圾收集器Serial 、Parallel、CMS、G1

这里介绍4个垃圾收集器,如果进行了错误的选择将会大大的影响程序的性能。

    时至今日,仍然有两个事情困扰着开发人员:垃圾收集(GC)和了解异性(程序猿的悲鸣),后者我确实不太了解,因为我被前者搞的无暇顾及怎么了解异性,特别是当知道在JAVA8中对这一区域有了很大的改进和提升还有移除了PermGen和以一些新的令人兴奋的调优。

    当我们谈到垃圾回收时,我们绝大多数都知道利用它的概念在我们日常的编程中。但是,当问题出现时,会发现很多是我们不知道的。JVM 最大误区之一就是它只有一个垃圾回收器,实际上是它提供了四个不同的收集器,每个都有其自身独特的优势和劣势。垃圾收集器不是自动选择的,这取决个人以及吞吐量和应用程序的差异。

这些垃圾收集的普遍存在的共同点是他们都把堆分隔成不同的片段来管理,比如在age-old区中的大多数对象应该被快速的回收。这些都是老生常谈的事,我们直接进入主题来看一下各个收集器的不同以及他们的优缺点。

1. Serial收集器

    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。

2. Parallel(并行)收集器

    这是 JVM 的缺省收集器。就像它的名字,其最大的优点是使用多个线程来通过扫描并压缩堆。串行收集器在GC时会停止其他所有工作线程(stop-the-world),CPU利用率是最高的,所以适用于要求高吞吐量(throughput)的应用,但停顿时间(pause time)会比较长,所以对web应用来说就不适合,因为这意味着用户等待时间会加长。而并行收集器可以理解是多线程串行收集,在串行收集基础上采用多线程方式进行GC,很好的弥补了串行收集的不足,可以大幅缩短停顿时间(如下图表示的停顿时长高度,并发比并行要短),因此对于空间不大的区域(如young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。

图1.Serial收集器与Parallel/ Throughput(并行)收集器的比较

3.CMS收集器

    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的资源。

4.G1收集器

    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对G1收集器的优化

    在java8 udpate 20中对G1收集器采用了字符串重复消除技术(String deduplication),之前字符串以及内部的char[]数组大量消耗了内存空间,在新的G1垃圾收集器中,将会对内存中重复的字符串进行优化,使他们指向同一个字符数组,以避免相同的字符串出现而使堆处理效率低下,你可以使用 -XX:+UseStringDeduplicationJVM参数来开启。

Java8和PermGen

    在 Java 8 最大的变化之一删除了在堆中为类的元数据、内部字符串和静态变量分配 permgen空间的部分。过去如果加载大量的类到内存中经常会出现内存溢出异常,并且开发人员需要在这个方面做大量的工作,所以如果这段通过JVM来管理了将是一个不错误的优化。

    每个垃圾收集器都有不同的配置参数,可以通过不同的参数来提升性能和降低吞吐量。这些都取决于你的应用需求,不同的对收集方式、可忍受的停顿时间、内存的大小都不一样,所以要根据自身的需求来定制不同的配置参数。

特别说明:尊重作者的劳动成果,转载请注明出处哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt378

时间: 2024-08-31 22:42:00

垃圾收集器Serial 、Parallel、CMS、G1的相关文章

G1垃圾收集器介绍

原文链接,译者:Greenster 简介 Oracle在JDK7 update 4之后开始完全支持G1垃圾收集器,G1是一个针对多处理器大容量内存的服务器端的垃圾收集器,其目标是在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求.G1在执行一些Java堆空间中的全区域操作(如:全局标记)时是和应用程序线程并发进行的,因此减少了Java堆空间的中断比例.(译者注:可简单理解为减少了Stop-the-World的时间比例) 技术说明 G1收集器通过下面一些方法实现了高性能和减少暂停时间的目的.

Garbage First(G1)垃圾收集器

引言:G1垃圾收集器采用一个略微不同的手段来解决并行.串行以及CMS GC的众多缺陷.对于大的Java堆来说,通过将Java堆拆分成一个个分区,G1会比其他垃圾收集器有更好的综合表现.本文选自<Java性能调优指南>. G1垃圾收集器采用一个略微不同的手段来解决并行.串行以及CMS GC的众多缺陷.G1将堆拆成一系列的分区,这样在一个时间段内,大部分的垃圾收集操作就只是在一个分区内执行,而不是整个堆或整个(老年)代. 在G1里,年轻代就是一系列的内存分区,这意味着不用再要求年轻代是一个连续的内

JAVA垃圾收集器与内存分配策略详解_java

引言 垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.垃圾收集技术需要考虑的三个问题是: 1.哪些内存需要回收 2.什么时候回收 3.如何回收 java内存运行时区域的分布,其中程序计数器,虚拟机栈,本地方法区都是随着线程而生,随线程而灭,所以这几个区域就不需要过多考虑回收问题.但是堆和方法区就不一样了,只有在程序运行期间我们才知道会创建哪些对象,这部分内存的分配和回收都是动态的.垃圾收集器所关注的就是这部分内存. 一 对象

jvm系列(三):GC算法 垃圾收集器

概述 垃圾收集 Garbage Collection 通常被称为"GC",它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计数器.虚拟机栈.本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的. 对象存活判断 判断对象是否存活一般有两种方式: 引用计数:每个对象有一个引用计数属性

深入理解JVM之四:详解垃圾收集器

前言 前面已经对垃圾收集算法有了较为详细的介绍,这里我们将对JVM中具体的垃圾回收器进行介绍,在虚拟机规范中并没有对垃圾回收器如何实现具体介绍,因此每个厂商的垃圾回收器可能会完全不同,但是我们介绍的是基于JDK1.7之后的Hotspot虚拟机(包括前面对Java虚拟机的介绍也是基于jdk1.7版本的).在Hotspot中,虚拟机的收集器主要有下: 可以看到垃圾收集器是按对象的分代来划分的,可以用线条连接的垃圾回收器表示两者可以配合使用.可以看到新生代垃圾收集器有Serial.ParNew.Par

垃圾收集器与内存分配策略

虚拟机如何判断对象是否存活? 1.引用计数算法   给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用的.   考虑一种情形:对象objA和objB都有字段instance,赋值令objA.instance=objB和objB.instance=objA;除此之外,这两个对象再无任何引用,实际上这两个对象以及不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收

我们的垃圾收集器

(译者注:这篇博文发表在2008年,虽然年代有些久远,但是文中说到的垃圾收集器我们至今还在使用,作者也谈到了对于G1垃圾收集器的期望.) 最近我在白板上给客户化了一个图表,他们似乎对这个有点兴趣,所以我想我可以重画一遍来给你们消遣. 每个蓝色的盒子都代表了一个收集器,用来收集某一代.黄色区域中的蓝色盒子是用来收集新生代的,灰色区域中的蓝色盒子是用来收集老年代的. Serial"是一个单线程.stop-the-world.使用复制算法的收集器. "ParNew" 是一个多线程.

java中HotSpot算法及垃圾收集器简介

垃圾回收算法基本思想: 1.枚举根节点(GC Roots) 在垃圾回收时,我们要想办法找出哪些对象是存活的,一般会选取一些被称为GC Root的对象,从这些对象开始枚举.枚举时要求所有对象停下来,也就是大家所称的"Stop the world".所有的算法实现都会将虚拟机停下来的,否则分析结果的准确性将无法保证.当执行系统停顿下来之后,虚拟机不需要遍历所有的根节点和上下文去确定GC Roots,而是存在着一个OopMap的数据结构来达到这个目的.在类加载完成的时候,虚拟机就会把什么类的

JVM垃圾收集器

参考文献:周志明<深入理解Java虚拟机>第二版 垃圾收集器就是 GC 具体的实现,不同厂商,不同版本,针对虚拟机不同的分区都有不一样垃圾收集器 图. 基于JDK 1.7 update 14的 HotSpot虚拟机包含的收集器 Serial 收集器 这个收集器是一个单线程收集器,在它进行收集工作时,必须暂停其他所有的工作线程,直到收集结束,这种工作方式又被称为"Stop the World" 它采用复制算法 因为单线程,所以会造成停顿,但也使得它简单而高效 适用于新生代 M