抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

JVM垃圾收集器

垃圾收集器

​ 如果说前面介绍的收集算法(JVM之垃圾回收-垃圾收集算法)是内存回收的抽象策略,那么垃圾收集器就是内存回收的具体实现。

​ JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。

​ 就像没有最好的算法一样,垃圾收集器也没有最好,只有最合适。我们能做的就是根据具体的应用场景选择最合适的垃圾收集器。

Serial收集器(复制算法)

一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。

​ 串行回收器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。在串行回收器进行垃圾回收时,Java 应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行回收器却是一个成熟、经过长时间生产环境考验的极为高效的 回收器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在 HotSpot 虚拟机中,使用-XX:+UseSerialGC 参数可以指定使用新生代串行回收器和老年代串行回收器。当 JVM 在 Client 模式下运行时,它是默认的垃圾回收器。老年代串行回收器使用的是标记-压缩算法。和新生代串行回收器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回 收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以 和多种新生代回收器配合使用,同时它也可以作为 CMS 回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。

Serial GC 的工作步骤如下所示:

特点
  • 针对新生代的收集器;
  • 采用复制算法;
  • 单线程收集;
  • 进行垃圾收集时,必须暂停所有工作线程,直到完成;
  • 即会”Stop The World”;
应用场景
  • 依然是HotSpot在Client模式下默认的新生代收集器;
  • 也有优于其他收集器的地方:
    • 简单高效(与其他收集器的单线程相比);
  • 对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
  • 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的
设置参数
1
2
添加该参数来显式的使用串行垃圾收集器:
"-XX:+UseSerialGC"

ParNew收集器(停止-复制算法)

新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

​ 并行回收器是工作在新生代的垃圾回收器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。
​ 并行回收器 也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。开启并行回收器可以使用参数-XX:+UseParNewGC,该参数设置新生代使用并行回收器,老年代使用串行回收器。老年代的并行回收回收器也是一种多线程并发的回收器。和新生代并行回收回收器一样,它也是一种关注吞吐量的回收器。老年代并行回收回收器使用标记-压缩算法,JDK1.6 之后开始启用。

​ ParNew 就是一个 Serial 的多线程版本,其它与Serial并无区别。ParNew 在单核 CPU 环境并不会比 Serial 收集器达到更好的效果,它默认开启的收集线程数和 CPU 数量一致,可以通过 -XX:ParallelGCThreads 来设置垃圾收集的线程数。

​ 如下是 ParNew 收集器和 Serial Old 收集器结合进行垃圾收集的示意图,当用户线程都执行到安全点时,所有线程暂停执行,ParNew 收集器以多线程,采用复制算法进行垃圾收集工作,收集完之后,用户线程继续开始执行。

特点
  • 除了多线程外,其余的行为、特点和Serial收集器一样;

  • 如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;

  • Serial收集器共用了不少代码;

应用场景
  • 在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;
  • 但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
设置参数
1
2
3
4
5
6
指定使用CMS后,会默认使用ParNew作为新生代收集:
"-XX:+UseConcMarkSweepGC"
强制指定使用ParNew:
"-XX:+UseParNewGC"
指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相:
"-XX:ParallelGCThreads"
为什么只有ParNew能与CMS收集器配合
  • CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
  • CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;
  • 因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;

Parallel GC(停止-复制算法)

​ Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。

​ 所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%,

​ 高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

​ Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的。使用 -XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收回收器,这是一对非常关注吞吐量的垃圾回收器组合,在对吞吐量敏感的系统中,可以考虑使用。参数 -XX:ParallelGCThreads 也可以用于设置垃圾回收时的线程数量。

​ 如下是 Parallel 收集器和 Parallel Old 收集器结合进行垃圾收集的示意图,在新生代,当用户线程都执行到安全点时,所有线程暂停执行,ParNew 收集器以多线程,采用复制算法进行垃圾收集工作,收集完之后,用户线程继续开始执行;在老年代,当用户线程都执行到安全点时,所有线程暂停执行,Parallel Old 收集器以多线程,采用标记整理算法进行垃圾收集工作。

特点
  • 新生代收集器;

  • 采用复制算法;

  • 多线程收集;

  • CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器的目则是达一个可控制的吞吐量(Throughput);

应用场景
  • 高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;
  • 当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;
  • 例如,那些执行批量处理、订单处理(对账等)、工资支付、科学计算的应用程序;
设置参数

Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:

控制最大垃圾收集停顿时间
1
"-XX:MaxGCPauseMillis"
  • 控制最大垃圾收集停顿时间,大于0的毫秒数;

  • MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;因为可能导致垃圾收集发生得更频繁;

设置垃圾收集时间占总时间的比率
1
"-XX:GCTimeRatio"
  • 设置垃圾收集时间占总时间的比率,0 < n < 100的整数;
  • GCTimeRatio相当于设置吞吐量大小;
  • 垃圾收集执行时间占应用程序执行时间的比例的计算方法是: 1 / (1 + n) 。
  • 例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5% = 1/(1+19);默认值是1% = 1/(1+99),即n=99;
  • 垃圾收集所花费的时间是年轻一代和老年代收集的总时间;
  • 如果没有满足吞吐量目标,则增加代的内存大小以尽量增加用户程序运行的时间;
GC自适应的调节策略(GC Ergonomics)
1
"-XX:+UseAdptiveSizePolicy" 

开启这个参数后,就不用手工指定一些细节参数,如:

  • 新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;
  • JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics);
  • 这是一种值得推荐的方式:
    1. 只需设置好内存数据大小(如”-Xmx”设置最大堆);
    2. 然后使用”-XX:MaxGCPauseMillis”或”-XX:GCTimeRatio”给JVM设置一个优化目标;
    3. 那些具体细节参数的调节就由JVM自适应完成;
    4. 这也是Parallel Scavenge收集器与ParNew收集器一个重要区别;

CMS(Concurrent Mark Sweep)收集器(标记-清理算法)

CMS 收集器是一种以最短回收停顿时间为目标的收集器,以 “ 最短用户线程停顿时间 ” 著称。

​ CMS( Concurrent Mark-Sweep ) 是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器,适用于对停顿比较敏感,并且有相对较多存活时间较长的对象(老年代较大)的应用程序;不过 CMS 虽然减少了回收的停顿时间,但是降低了堆空间的利用率。CMS GC 采用了 Mark-Sweep 算法,因此经过CMS收集的堆会产生空间碎片;为了解决堆空间浪费问题,CMS回收器不再采用简单的指针指向一块可用堆空间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当 JVM 分配对象空间的时候,会搜索这个列表找到足够大的空间来存放住这个对象。另一方面,由于 CMS 线程和应用程序线程并发执行,CMS GC 需要更多的 CPU 资源。同时,因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在CMS回收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用68%的时候,CMS就开始行动了。 – XX:CMSInitiatingOccupancyFraction =n 来设置这个阀值。

CMS GC 工作步骤如下所示:

适用场景:重视服务器响应速度,要求系统停顿时间最短。可以使用 -XX:+UserConMarkSweepGC 来选择 CMS 作为老年代收集器。

垃圾收集过程

CMS 收集器是一种以最短回收停顿时间为目标的收集器,以 “ 最短用户线程停顿时间 ” 著称。整个垃圾收集过程分为 4 个步骤:

  • 初始标记(STW initial mark):在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的”根对象”开始,只扫描到能够和”根对象”直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。

  • 并发标记(Concurrent marking):这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。

  • 并发预清理(Concurrent precleaning):并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代,或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段”重新标记”的工作,因为下一个阶段会Stop The World。

  • 重新标记(STW remark):这个阶段会暂停虚拟机,回收器线程扫描在CMS堆中剩余的对象。扫描从”跟对象”开始向下追溯,并处理对象关联。

  • 并发清理(Concurrent sweeping):清理垃圾对象,这个阶段回收器线程和应用程序线程并发执行。

  • 并发重置(Concurrent reset):这个阶段,重置CMS回收器的数据结构,等待下一次垃圾回收。

特点
  • 针对老年代
  • 基于”标记-清除”算法(不进行压缩操作,会产生内存碎片)
  • 以获取最短回收停顿时间为目标
  • 并发收集、低停顿
  • 需要更多的内存
  • CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;
    第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
应用场景
  • 与用户交互较多的场景;(如常见WEB、B/S-浏览器/服务器模式系统的服务器上的应用)
  • 希望系统停顿时间最短,注重服务的响应速度;
  • 以给用户带来较好的体验;
设置参数
1
2
指定使用CMS收集器
"-XX:+UseConcMarkSweepGC"
缺点
对CPU资源敏感

​ 面向并发设计的程序都对CPU资源比较敏感(并发程序的特点)。在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。(在对账系统中,不适合使用CMS收集器)。

​ CMS的默认收集线程数量是=(CPU数量+3)/4; 当CPU数量越多,回收的线程占用CPU就少。
也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。(比如 CPU=2时,那么就启动一个线程回收,占了50%的CPU资源。)
(一个回收线程会在回收期间一直占用CPU资源)

​ 针对这种情况,曾出现了”增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS);
类似使用抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程运行时间;
但效果并不理想,JDK1.6后就官方不再提倡用户使用。

无法处理浮动垃圾

​ 无法处理浮动垃圾,可能出现”Concurrent Mode Failure”失败
​ 在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;

解决办法
这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;
也可以认为CMS所需要的空间比其他垃圾收集器大; 可以使用”-XX:CMSInitiatingOccupancyFraction”,设置CMS预留老年代内存空间; (详解见名词解释)

产生大量内存碎片

​ 由于CMS是基于“标记+清除”算法来回收老年代对象的,因此长时间运行后会产生大量的空间碎片问题,可能导致新生代对象晋升到老生代失败。

​ 由于碎片过多,将会给大对象的分配带来麻烦。因此会出现这样的情况,老年代还有很多剩余的空间,但是找不到连续的空间来分配当前对象,这样不得不提前触发一次Full GC。

解决办法

​ 使用”-XX:+UseCMSCompactAtFullCollection”和”-XX:+CMSFullGCsBeforeCompaction”,需要结合使用。

UseCMSCompactAtFullCollection

1
"-XX:+UseCMSCompactAtFullCollection"

​ 为了解决空间碎片问题,CMS收集器提供−XX:+UseCMSCompactAlFullCollection标志,使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程;

  • 但合并整理过程无法并发,停顿时间会变长;

  • 默认开启(但不会进行,需要结合CMSFullGCsBeforeCompaction使用)\

    CMSFullGCsBeforeCompaction
    由于合并整理是无法并发执行的,空间碎片问题没有了,但是有导致了连续的停顿。因此,可以使用另一个参数−XX:CMSFullGCsBeforeCompaction,表示在多少次不压缩的Full GC之后,对空间碎片进行压缩整理。
    ​ 可以减少合并整理过程的停顿时间;默认为0,也就是说每次都执行Full GC,不会进行压缩整理;

    ​ 由于空间不再连续,CMS需要使用可用”空闲列表”内存分配方式,这比简单实用”碰撞指针”分配内存消耗大;

CMS&Parallel Old

​ 总体来看,CMS与Parallel Old垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间;

​ 但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间;
(原因:CMS不进行内存空间整理节省了时间,但是可用空间不再是连续的了,垃圾收集也不能简单的使用指针指向下一次可用来为对象分配内存的地址了。相反,这种情况下,需要使用可用空间列表。即,会创建一个指向未分配区域的列表,每次为对象分配内存时,会从列表中找到一个合适大小的内存区域来为新对象分配内存。这样做的结果是,老年代上的内存的分配比简单实用碰撞指针分配内存消耗大。这也会增加年轻代垃圾收集的额外负担,因为老年代中的大部分对象是在新生代垃圾收集的时候从新生代提升为老年代的。)
当新生代对象无法分配过大对象,就会放到老年代进行分配。

G1 GC

​ G1 GC 是 JDK 1.7 中正式投入使用的用于取代 CMS 的压缩回收器,它虽然没有在物理上隔断新生代与老生代,但是仍然属于分代垃圾回收器;G1 GC 仍然会区分年轻代与老年代,年轻代依然分有 Eden 区与 Survivor 区。G1 GC 首先将堆分为大小相等的 Region,避免全区域的垃圾收集,然后追踪每个 Region 垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;同时 G1 GC 采用 Remembered Set 来存放 Region 之间的对象引用以及其他回收器中的新生代与老年代之间的对象引用,从而避免全堆扫描。G1 GC 的分区示例如下图所示:

​ 随着 G1 GC 的出现,Java 垃圾回收器通过引入 Region 的概念,从传统的连续堆内存布局设计,逐步走向了物理上不连续但是逻辑上依旧连续的内存块;这样我们能够将某个 Region 动态地分配给 Eden、Survivor、老年代、大对象空间、空闲区间等任意一个。每个 Region 都有一个关联的 Remembered Set(简称RS),RS 的数据结构是 Hash 表,里面的数据是 Card Table (堆中每 512byte 映射在 card table 1byte)。简单的说RS里面存在的是Region中存活对象的指针。当Region中数据发生变化时,首先反映到Card Table中的一个或多个Card上,RS通过扫描内部的Card Table得知Region中内存使用情况和存活对象。在使用Region过程中,如果Region被填满了,分配内存的线程会重新选择一个新的Region,空闲Region被组织到一个基于链表的数据结构(LinkedList)里面,这样可以快速找到新的Region。

​ 每一个方块就是一个区域,每个区域可能是 Eden、Survivor、老年代,每种区域的数量也不一定。JVM 启动时会自动设置每个区域的大小(1M ~ 32M,必须是 2 的次幂),最多可以设置 2048 个区域(即支持的较大堆内存为 32M*2048 = 64G),假如设置 -Xmx8g -Xms8g,则每个区域大小为 8g/2048=4M。

​ 为了在 GC Roots Tracing 的时候避免扫描全堆,在每个 Region 中,都有一个 Remembered Set 来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个 Remembered Set 来实时记录与其他区域的引用关系),在标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据。

​ G1 收集器可以 “ 建立可预测的停顿时间模型 ”,它维护了一个列表用于记录每个 Region 回收的价值大小(回收后获得的空间大小以及回收所需时间的经验值),这样可以保证 G1 收集器在有限的时间内可以获得较大的回收效率。

​ 适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 默认使用 G1 收集器。

模式
Young GC

​ 选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。(复制回收算法)

Mixed GC

​ 选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。

​ Mixed GC不是full GC,它只能回收部分老年代的Region。如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。

工作步骤
  • 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。

  • 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。

  • 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。

  • 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。

特点
  • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力;

  • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况;

  • 分代GC:G1依然是一个分代回收器,但是和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;

  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度。

  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。

应用场景
  • 面向服务端应用,针对具有大内存、多处理器的机器;
  • 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;
    • 如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;
      (实践:对账系统中将CMS垃圾收集器修改为G1,降低对账时间20秒以上)
如何选择

具体什么情况下应用G1垃圾收集器比CMS好,可以参考以下几点(但不是绝对):

  • 超过50%的Java堆被活动数据占用;
  • 对象分配频率或年代的提升频率变化很大;
  • GC停顿时间过长(长于0.5至1秒);
建议
  • 如果现在采用的收集器没有出现问题,不用急着去选择G1;
  • 如果应用程序追求低停顿,可以尝试选择G1;
  • 是否代替CMS只有需要实际场景测试才知道。(如果使用G1后发现性能还没有使用CMS好,那么还是选择CMS比较好)
设置参数

可以通过下面的参数,来设置一些G1相关的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
指定使用G1收集器:
"-XX:+UseG1GC"

当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45:
"-XX:InitiatingHeapOccupancyPercent"

为G1设置暂停时间目标,默认值为200毫秒:
"-XX:MaxGCPauseMillis"

设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region:
"-XX:G1HeapRegionSize"

新生代最小值,默认值5%:
"-XX:G1NewSizePercent"

新生代最大值,默认值60%:
"-XX:G1MaxNewSizePercent"

设置STW期间,并行GC线程数:
"-XX:ParallelGCThreads"

设置并发标记阶段,并行执行的线程数:
"-XX:ConcGCThreads"
主要的参数
参数 含义
-XX:G1HeapRegionSize=n 设置Region大小,并非最终值
-XX:MaxGCPauseMillis 设置G1收集过程目标时间,默认值200ms,不是硬性条件
-XX:G1NewSizePercent 新生代最小值,默认值5%
-XX:G1MaxNewSizePercent 新生代最大值,默认值60%
-XX:ParallelGCThreads STW期间,并行GC线程数
-XX:ConcGCThreads=n 并发标记阶段,并行执行的线程数
-XX:InitiatingHeapOccupancyPercent 设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

GC是什么时候触发的

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

Scavenge GC

​ 一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

​ 对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

  • 年老代(Tenured)被写满;

  • 持久代(Perm)被写满;

  • System.gc()被显示调用;

  • 上一次GC之后Heap的各域分配策略动态变化

Stop The World现象

Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。

​ JVM里有一条特殊的线程--VM Threads,专门用来执行一些特殊的VM Operation,比如分派GC,thread dump等,这些任务,都需要整个Heap,以及所有线程的状态是静止的,一致的才能进行。所以JVM引入了安全点(Safe Point)的概念,想办法在需要进行VM Operation时,通知所有的线程进入一个静止的安全点。

​ GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数。

各个收集器对比

名称 收集算法 工作区域 可配合对象 线程 并发 适用场合 优缺点
Serial 复制算法 新生代 CMS;Serial Old 单CPU;Client模式下 缺:stop the world;优:简单高效,没有线程交互开销,专注于GC;
ParNew 复制算法 新生代 CMS;Serial Old 多CPU;Server模式下 缺:stop the world优:并行并发GC
Parallel Scavenge 复制算法 新生代 Serial Old;ParallelScavenge 吞吐量控制,Client,server均可以 主要关注吞吐量,通过吞吐量的设置控制停顿时间,适应不同的场景
Serial Old 标记整理算法 老年代 Serial,ParNew,Parallel Scavenge 主要Client模式下 缺:stop the world
Parallel Old 复制算法(Parallel Scavenge老年代版本) 老年代 Parallel Scavenge 吞吐量控制,Client,server均可以 主要关注吞吐量,通过吞吐量的设置控制停顿时间,适应不同的场景
CMS(Concurretn Mark Sweep) 标记清除算法 老年代 Serial,ParNew,Serial Old是其备选方案 互联网站;B/S系统服务端 缺:CPU资源敏感,无法处理浮动垃圾,产生大量内存碎片优:并发收集,低停顿
G1 整体基于标记整理算法 新生代&老年代 面向服务端应用 优:并行与并发,分代收集,空间整合(标记整理算法),可预测停顿

评论