jvm参数详解

JVM 实用参数-5 新生代垃圾回收

http://ifeve.com/useful-jvm-flags-part-5-young-generation-garbage-collection/

本部分,我们将关注堆(heap) 中一个主要区域,新生代(young generation)。首先我们会讨论为什么调整新生代的参数会对应用的性能如此重要,接着我们将学习新生代相关的JVM参数。

单纯从JVM的功能考虑,并不需要新生代,完全可以针对整个堆进行操作。新生代存在的唯一理由是优化垃圾回收(GC)的性能。更具体说,把堆划分为 新生代和老年代有2个好处:简化了新对象的分配(只在新生代分配内存),可以更有效的清除不再需要的对象(即死对象)(新生代和老年代使用不同的GC算 法)

通过广泛研究面向对象实现的应用,发现一个共同特点:很多对象的生存时间都很短。同时研究发现,新生对象很少引用生存时间长的对象。结合这2个特 点,很明显 GC 会频繁访问新生对象,例如在堆中一个单独的区域,称之为新生代。在新生代中,GC可以快速标记回收”死对象”,而不需要扫描整个Heap中的存活一段时间 的”老对象”。

 

SUN/Oracle 的HotSpot JVM 又把新生代进一步划分为3个区域:一个相对大点的区域,称为”伊甸园区(Eden)”;两个相对小点的区域称为”From 幸存区(survivor)”和”To 幸存区(survivor)”。按照规定,新对象会首先分配在 Eden 中(如果新对象过大,会直接分配在老年代中)。在GC中,Eden 中的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过GC的次数),会被移动到老年代。

基于大多数新生对象都会在GC中被收回的假设。新生代的GC 使用复制算法。在GC前To 幸存区(survivor)保持清空,对象保存在 Eden 和 From 幸存区(survivor)中,GC运行时,Eden中的幸存对象被复制到 To 幸存区(survivor)。针对 From 幸存区(survivor)中的幸存对象,会考虑对象年龄,如果年龄没达到阀值(tenuring threshold),对象会被复制到To 幸存区(survivor)。如果达到阀值对象被复制到老年代。复制阶段完成后,Eden 和From 幸存区中只保存死对象,可以视为清空。如果在复制过程中To 幸存区被填满了,剩余的对象会被复制到老年代中。最后 From 幸存区和 To幸存区会调换下名字,在下次GC时,To 幸存区会成为From 幸存区。

https://blog.codecentric.de/files/2011/08/young_gc.png
https://blog.codecentric.de/files/2011/08/young_gc.png
上图演示GC过程,黄色表示死对象,绿色表示剩余空间,红色表示幸存对象

总结一下,对象一般出生在Eden区,年轻代GC过程中,对象在2个幸存区之间移动,如果对象存活到适当的年龄,会被移动到老年代。当对象在老年代死亡时,就需要更高级别的GC,更重量级的GC算法(复制算法不适用于老年代,因为没有多余的空间用于复制)

现在应该能理解为什么新生代大小非常重要了(译者,有另外一种说法:新生代大小并不重要,影响GC的因素主要是幸存对象的数量),如果新生代过小, 会导致新生对象很快就晋升到老年代中,在老年代中对象很难被回收。如果新生代过大,会发生过多的复制过程。我们需要找到一个合适大小,不幸的是,要想获得 一个合适的大小,只能通过不断的测试调优。这就需要JVM参数了

-XX:NewSize and -XX:MaxNewSize
就像可以通过参数(-Xms and -Xmx) 指定堆大小一样,可以通过参数指定新生代大小。设置 XX:MaxNewSize 参数时,应该考虑到新生代只是整个堆的一部分,新生代设置的越大,老年代区域就会减少。一般不允许新生代比老年代还大,因为要考虑GC时最坏情况,所有对 象都晋升到老年代。(译者:会发生OOM错误) -XX:MaxNewSize 最大可以设置为-Xmx/2 .

考虑性能,一般会通过参数 -XX:NewSize 设置新生代初始大小。如果知道新生代初始分配的对象大小(经过监控) ,这样设置会有帮助,可以节省新生代自动扩展的消耗。

-XX:NewRatio
可以设置新生代和老年代的相对大小。这种方式的优点是新生代大小会随着整个堆大小动态扩展。参数 -XX:NewRatio 设置老年代与新生代的比例。例如 -XX:NewRatio=3 指定老年代/新生代为3/1. 老年代占堆大小的 3/4 ,新生代占 1/4 .

如果针对新生代,同时定义绝对值和相对值,绝对值将起作用。下面例子:
$ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp

以上设置, JVM 会尝试为新生代分配四分之一的堆大小,但不会小于32MB或大于521MB

在设置新生代大小问题上,使用绝对值还是相对值,不存在通用准则 。如果了解应用的内存使用情况,设置固定大小的堆和新生代更有利,当然也可以设置相对值。如果对应用的内存使用一无所知,正确的做法是不要设置任何参数, 如果应用运行良好。很好,我们不用做任何额外动作.如果遇到性能或OutOfMemoryErrors, 在调优之前,首先需要进行一系列有目的的监控测试,缩小问题的根源。

-XX:SurvivorRatio
参数 -XX:SurvivorRatio 与 -XX:NewRatio 类似,作用于新生代内部区域。-XX:SurvivorRatio 指定伊甸园区(Eden)与幸存区大小比例. 例如, -XX:SurvivorRatio=10 表示伊甸园区(Eden)是 幸存区To 大小的10倍(也是幸存区From的10倍).所以,伊甸园区(Eden)占新生代大小的10/12, 幸存区From和幸存区To 每个占新生代的1/12 .注意,两个幸存区永远是一样大的..

设定幸存区大小有什么作用? 假设幸存区相对伊甸园区(Eden)太小, 相应新生对象的伊甸园区(Eden)永远很大空间, 我们当然希望,如果这些对象在GC时全部被回收,伊甸园区(Eden)被清空,一切正常.然而,如果有一部分对象在GC中幸存下来, 幸存区只有很少空间容纳这些对象.结果大部分幸存对象在一次GC后,就会被转移到老年代 ,这并不是我们希望的.考虑相反情况, 假设幸存区相对伊甸园区(Eden)太大,当然有足够的空间,容纳GC后的幸存对象. 但是过小的伊甸园区(Eden),意味着空间将越快耗尽,增加新生代GC次数,这是不可接受的。

总之,我们希望最小化短命对象晋升到老年代的数量,同时也希望最小化新生代GC 的次数和持续时间.我们需要找到针对当前应用的折中方案, 寻找适合方案的起点是 了解当前应用中对象的年龄分布情况。

-XX:+PrintTenuringDistribution
参数 -XX:+PrintTenuringDistribution 指定JVM 在每次新生代GC时,输出幸存区中对象的年龄分布。例如:
Desired survivor size 75497472 bytes, new threshold 15 (max 15)
- age 1: 19321624 bytes, 19321624 total
- age 2: 79376 bytes, 19401000 total
- age 3: 2904256 bytes, 22305256 total

第一行说明幸存区To大小为 75 MB. 也有关于老年代阀值(tenuring threshold)的信息, 老年代阀值,意思是对象从新生代移动到老年代之前,经过几次GC(即, 对象晋升前的最大年龄). 上例中,老年代阀值为15,最大也是15.

之后行表示,对于小于老年代阀值的每一个对象年龄,本年龄中对象所占字节 (如果当前年龄没有对象,这一行会忽略). 上例中,一次 GC 后幸存对象大约 19 MB, 两次GC 后幸存对象大约79 KB , 三次GC 后幸存对象大约 3 MB .每行结尾,显示直到本年龄全部对象大小.所以,最后一行的 total 表示幸存区To 总共被占用22 MB . 幸存区To 总大小为 75 MB ,当前老年代阀值为15,可以断定在本次GC中,没有对象会移动到老年代。现在假设下一次GC 输出为:

Desired survivor size 75497472 bytes, new threshold 2 (max 15)
- age 1: 68407384 bytes, 68407384 total
- age 2: 12494576 bytes, 80901960 total
- age 3: 79376 bytes, 80981336 total
- age 4: 2904256 bytes, 83885592 total

对比前一次老年代分布。明显的,年龄2和年龄3 的对象还保持在幸存区中,因为我们看到年龄3和4的对象大小与前一次年龄2和3的相同。同时发现幸存区中,有一部分对象已经被回收,因为本次年龄2的对象 大小为 12MB ,而前一次年龄1的对象大小为 19 MB。最后可以看到最近的GC中,有68 MB 新对象,从伊甸园区移动到幸存区。

注意,本次GC 幸存区占用总大小 84 MB -大于75 MB. 结果,JVM 把老年代阀值从15降低到2,在下次GC时,一部分对象会强制离开幸存区,这些对象可能会被回收(如果他们刚好死亡)或移动到老年代。

-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio
参数 -XX:+PrintTenuringDistribution 输出中的部分值可以通过其它参数控制。通过 -XX:InitialTenuringThreshold 和 -XX:MaxTenuringThreshold 可以设定老年代阀值的初始值和最大值。另外,可以通过参数 -XX:TargetSurvivorRatio 设定幸存区的目标使用率.例如 , -XX:MaxTenuringThreshold=10 -XX:TargetSurvivorRatio=90 设定老年代阀值的上限为10,幸存区空间目标使用率为90%。

有多种方式,设置新生代行为,没有通用准则。我们必须清楚以下2中情况:
1 如果从年龄分布中发现,有很多对象的年龄持续增长,在到达老年代阀值之前。这表示 -XX:MaxTenuringThreshold 设置过大
2 如果 -XX:MaxTenuringThreshold 的值大于1,但是很多对象年龄从未大于1.应该看下幸存区的目标使用率。如果幸存区使用率从未到达,这表示对象都被GC回收,这正是我们想要的。 如果幸存区使用率经常达到,有些年龄超过1的对象被移动到老年代中。这种情况,可以尝试调整幸存区大小或目标使用率。

-XX:+NeverTenure and -XX:+AlwaysTenure
最后,我们介绍2个颇为少见的参数,对应2种极端的新生代GC情况.设置参数 -XX:+NeverTenure , 对象永远不会晋升到老年代.当我们确定不需要老年代时,可以这样设置。这样设置风险很大,并且会浪费至少一半的堆内存。相反设置参数 -XX:+AlwaysTenure, 表示没有幸存区,所有对象在第一次GC时,会晋升到老年代。
没有合理的场景使用这个参数。可以在测试环境中,看下这样设置会发生什么有趣的事.但是并不推荐使用这些参数.

结论
适当的配置新生代非常重要,有相当多的参数可以设置新生代。然而,单独调整新生代,而不考虑老年代是不可能优化成功的。当调整堆和GC设置时,我们总是应该同时考虑新生代和老年代。

在本系列的下面2部分,我们将讨论 HotSpot JVM 中老年代 GC 策略,我们会学习“吞吐量GC收集器” 和 “并发低延迟GC收集器”,也会了解收集器的基本准则,算法和调整参数.

 

 

 

在实践中我们发现对于大多数的应用领域,评估一个垃圾收集(GC)算法如何根据如下两个标准:

  1. 吞吐量越高算法越好
  2. 暂停时间越短算法越好

首先让我们来明确垃圾收集(GC)中的两个术语:吞吐量(throughput)和暂停时间(pause times)。 JVM在专门的线程(GC threads)中执行GC。 只要GC线程是活动的,它们将与应用程序线程(application threads)争用当前可用CPU的时钟周期。 简单点来说,吞吐量是指应用程序线程用时占程序总用时的比例。 例如,吞吐量99/100意味着100秒的程序执行时间应用程序线程运行了99秒, 而在这一时间段内GC线程只运行了1秒。

术语”暂停时间”是指一个时间段内应用程序线程让与GC线程执行而完全暂停。 例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的。 如果说一个正在运行的应用程序有100毫秒的“平均暂停时间”,那么就是说该应用程序所有的暂停时间平均长度为100毫秒。 同样,100毫秒的“最大暂停时间”是指该应用程序所有的暂停时间最大不超过100毫秒。

吞吐量 VS 暂停时间

高吞吐量最好因为这会让应用程序的最终用户感觉只有应用程序线程在做“生产性”工作。 直觉上,吞吐量越高程序运行越快。 低暂停时间最好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的。 这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。 因此,具有低的最大暂停时间是非常重要的,特别是对于一个交互式应用程序。

不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。这样想想看,为了清晰起见简化一下:GC需要一定的前提条件以便安全地运行。 例如,必须保证应用程序线程在GC线程试图确定哪些对象仍然被引用和哪些没有被引用的时候不修改对象的状态。 为此,应用程序在GC期间必须停止(或者仅在GC的特定阶段,这取决于所使用的算法)。 然而这会增加额外的线程调度开销:直接开销是上下文切换,间接开销是因为缓存的影响。 加上JVM内部安全措施的开销,这意味着GC及随之而来的不可忽略的开销,将增加GC线程执行实际工作的时间。 因此我们可以通过尽可能少运行GC来最大化吞吐量,例如,只有在不可避免的时候进行GC,来节省所有与它相关的开销。

然而,仅仅偶尔运行GC意味着每当GC运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。 单个GC需要花更多时间来完成, 从而导致更高的平均和最大暂停时间。 因此,考虑到低暂停时间,最好频繁地运行GC以便更快速地完成。 这反过来又增加了开销并导致吞吐量下降,我们又回到了起点。
综上所述,在设计(或使用)GC算法时​​,我们必须确定我们的目标:一个GC算法​​只可能针对两个目标之一(即只专注于最大吞吐量或最小暂停时间),或尝试找到一个二者的折衷。

HotSpot虚拟机上的垃圾收集

该系列的第五部分我们已经讨论过年轻代的垃圾收集器。 对于年老代,HotSpot虚拟机提供两类垃圾收集算法(除了新的G1垃圾收集算法),第一类算法试图最大限度地提高吞吐量,而第二类算法试图最小化暂停 时间。 今天我们的重点是第一类,”面向吞吐量”的垃圾收集算法。
我们希望把重点放在JVM配置参数上,所以我只会简要概述HotSpot提供的面向吞吐量(throughput-oriented)垃圾收集算法。 当年老代中由于缺乏空间导致对象分配失败时会触发垃圾收集器(事实上,”分配”的通常是指从年轻代提升到年老代的对象)。 从所谓的”GC根”(GC roots)开始,搜索堆中的可达对象并将其标记为活着的,之后,垃圾收集器将活着的对象移到年老代的一块无碎片(non-fragmented)内存块 中,并标记剩余的内存空间是空闲的。 也就是说,我们不像复制策略那样移到一个不同的堆区域,像年轻代垃圾收集算法所做的那样。 相反地,我们把所有的对象放在一个堆区域中,从而对该堆区域进行碎片整理。 垃圾收集器使用一个或多个线程来执行垃圾收集。 当使用多个线程时,算法的不同步骤被分解,使得每个收集线程大多时候工作在自己的区域而不干扰其他线程。 在垃圾收集期间,所有的应用程序线程暂停,只有垃圾收集完成之后才会重新开始。 现在让我们来看看跟面向吞吐量垃圾收集算法有关的重要JVM配置参数。

-XX:+UseSerialGC

我们使用该标志来激活串行垃圾收集器,例如单线程面向吞吐量垃圾收集器。 无论年轻代还是年老代都将只有一个线程执行垃圾收集。 该标志被推荐用于只有单个可用处理器核心的JVM。 在这种情况下,使用多个垃圾收集线程甚至会适得其反,因为这些线程将争用CPU资源,造成同步开销,却从未真正并行运行。

-XX:+UseParallelGC

有了这个标志,我们告诉JVM使用多线程并行执行年轻代垃圾收集。 在我看来,Java 6中不应该使用该标志因为-XX:+UseParallelOldGC显然更合适。 需要注意的是Java 7中该情况改变了一点(详见本概述),就是-XX:+UseParallelGC能达到-XX:+UseParallelOldGC一样的效果。

-XX:+UseParallelOldGC

该标志的命名有点不巧,因为”老”听起来像”过时”。 然而,”老”实际上是指年老代,这也解释了为什么-XX:+UseParallelOldGC要优于-XX:+UseParallelGC:除了激活年轻 代并行垃圾收集,也激活了年老代并行垃圾收集。 当期望高吞吐量,并且JVM有两个或更多可用处理器核心时,我建议使用该标志。
作为旁注,HotSpot的并行面向吞吐量垃圾收集算法通常称为”吞吐量收集器”,因为它们旨在通过并行执行来提高吞吐量。

-XX:ParallelGCThreads

通过-XX:ParallelGCThreads=<value>我们可以指定并行垃圾收集的线程数量。 例如,-XX:ParallelGCThreads=6表示每次并行垃圾收集将有6个线程执行。 如果不明确设置该标志,虚拟机将使用基于可用(虚拟)处理器数量计算的默认值。 决定因素是由Java Runtime。availableProcessors()方法的返回值N,如果N<=8,并行垃圾收集器将使用N个垃圾收集线程,如果N& gt;8个可用处理器,垃圾收集线程数量应为3+5N/8。
当JVM独占地使用系统和处理器时使用默认设置更有意义。 但是,如果有多个JVM(或其他耗CPU的系统)在同一台机器上运行,我们应该使用-XX:ParallelGCThreads来减少垃圾收集线程数到一 个适当的值。 例如,如果4个以服务器方式运行的JVM同时跑在在一个具有16核处理器的机器上,设置-XX:ParallelGCThreads=4是明智的,它能使 不同JVM的垃圾收集器不会相互干扰。

-XX:-UseAdaptiveSizePolicy

吞吐量垃圾收集器提供了一个有趣的(但常见,至少在现代JVM上)机制以提高垃圾收集配置的用户友好性。 这种机制被看做是HotSpot在Java 5中引入的”人体工程学”概念的一部分。 通过人体工程学,垃圾收集器能将堆大小动态变动像GC设置一样应用到不同的堆区域,只要有证据表明这些变动将能提高GC性能。 “提高GC性能”的确切含义可以由用户通过-XX:GCTimeRatio和-XX:MaxGCPauseMillis(见下文)标记来指定。
重要的是要知道人体工程学是默认激活的。 这很好,因为自适应行为是JVM最大优势之一。 不过,有时我们需要非常清楚对于特定应用什么样的设置是最合适的,在这些情况下,我们可能不希望JVM混乱我们的设置。 每当我们发现处于这种情况时,我们可以考虑通过-XX:-UseAdaptiveSizePolicy停用一些人体工程学。

-XX:GCTimeRatio

通过-XX:GCTimeRatio=<value>我们告诉JVM吞吐量要达到的目标值。 更准确地说,-XX:GCTimeRatio=N指定目标应用程序线程的执行时间(与总的程序执行时间)达到N/(N+1)的目标比值。 例如,通过-XX:GCTimeRatio=9我们要求应用程序线程在整个执行时间中至少9/10是活动的(因此,GC线程占用其余1/10)。 基于运行时的测量,JVM将会尝试修改堆和GC设置以期达到目标吞吐量。 -XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间。

-XX:MaxGCPauseMillis

通过-XX:GCTimeRatio=<value>告诉JVM最大暂停时间的目标值(以毫秒为单位)。 在运行时,吞吐量收集器计算在暂停期间观察到的统计数据(加权平均和标准偏差)。 如果统计表明正在经历的暂停其时间存在超过目标值的风险时,JVM会修改堆和GC设置以降低它们。 需要注意的是,年轻代和年老代垃圾收集的统计数据是分开计算的,还要注意,默认情况下,最大暂停时间没有被设置。
如果最大暂停时间和最小吞吐量同时设置了目标值,实现最大暂停时间目标具有更高的优先级。 当然,无法保证JVM将一定能达到任一目标,即使它会努力去做。 最后,一切都取决于手头应用程序的行为。
当设置最大暂停时间目标时,我们应注意不要选择太小的值。 正如我们现在所知道的,为了保持低暂停时间,JVM需要增加GC次数,那样可能会严重影响可达到的吞吐量。 这就是为什么对于要求低暂停时间作为主要目标的应用程序(大多数是Web应用程序),我会建议不要使用吞吐量收集器,而是选择CMS收集器。 CMS收集器是本系列下一部分的主题。

 

 

 

HotSpot JVM的并发标记清理收集器(CMS收集器)的主要目标就是:低应用停顿时间。该目标对于大多数交互式应用很重要,比如web应用。在我们看一下有关JVM的参数之前,让我们简要回顾CMS收集器的操作和使用它时可能出现的主要挑战。

就像吞吐量收集器(参见本系列的第6部分),CMS 收集器处理老年代的对象,然而其操作要复杂得多。吞吐量收集器总是暂停应用程序线程,并且可能是相当长的一段时间,然而这能够使该算法安全地忽略应用程 序。相比之下,CMS收集器被设计成在大多数时间能与应用程序线程并行执行,仅仅会有一点(短暂的)停顿时间。GC与应用程序并行的缺点就是,可能会出现 各种同步和数据不一致的问题。为了实现安全且正确的并发执行,CMS收集器的GC周期被分为了好几个连续的阶段。

CMS收集器的过程

CMS收集器的GC周期由6个阶段组成。其中4个阶段(名字以Concurrent开始的)与实际的应用程序是并发执行的,而其他2个阶段需要暂停应用程序线程。

  1. 初始标记:为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。
  2. 并发标记:从第一阶段收集到的对象引用开始,遍历所有其他的对象引用。
  3. 并发预清理:改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。
  4. 重标记:由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。
  5. 并发清理:所有不再被应用的对象将从堆里清除掉。
  6. 并发重置:收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。

一个常见的误解是,CMS收集器运行是完全与应用程序并发的。我们已经看到,事实并非如此,即使“stop-the-world”阶段相对于并发阶段的时间很短。

应该指出,尽管CMS收集器为老年代垃圾回收提供了几乎完全并发的解决方案,然而年轻代仍然通过“stop-the-world”方法来进行收集。对于交互式应用,停顿也是可接受的,背后的原理是年轻带的垃圾回收时间通常是相当短的。

挑战

当我们在真实的应用中使用CMS收集器时,我们会面临两个主要的挑战,可能需要进行调优:

  1. 堆碎片
  2. 对象分配率高

堆碎片是有可能的,不像吞吐量收集器,CMS收集器并没有任何碎片整理的机制。因此,应用程序有可能出现这样的情形,即使总的堆大小远没有耗尽,但 却不能分配对象——仅仅是因为没有足够连续的空间完全容纳对象。当这种事发生后,并发算法不会帮上任何忙,因此,万不得已JVM会触发Full GC。回想一下,Full GC 将运行吞吐量收集器的算法,从而解决碎片问题——但却暂停了应用程序线程。因此尽管CMS收集器带来完全的并发性,但仍然有可能发生长时间的“stop- the-world”的风险。这是“设计”,而不能避免的——我们只能通过调优收集器来它的可能性。想要100%保证避免”stop-the- world”,对于交互式应用是有问题的。

第二个挑战就是应用的对象分配率高。如果获取对象实例的频率高于收集器清除堆里死对象的频率,并发算法将再次失败。从某种程度上说,老年代将没有足 够的可用空间来容纳一个从年轻代提升过来的对象。这种情况被称为“并发模式失败”,并且JVM会执行堆碎片整理:触发Full GC。

当这些情形之一出现在实践中时(经常会出现在生产系统中),经常被证实是老年代有大量不必要的对象。一个可行的办法就是增加年轻代的堆大小,以防止 年轻代短生命的对象提前进入老年代。另一个办法就似乎利用分析器,快照运行系统的堆转储,并且分析过度的对象分配,找出这些对象,最终减少这些对象的申 请。

下面我看看大多数与CMS收集器调优相关的JVM标志参数。

-XX:+UseConcMarkSweepGC

该标志首先是激活CMS收集器。默认HotSpot JVM使用的是并行收集器。

-XX:UseParNewGC

当使用CMS收集器时,该标志激活年轻代使用多线程并行执行垃圾回收。这令人很惊讶,我们不能简单在并行收集器中重用 -XX:UserParNewGC标志,因为概念上年轻代用的算法是一样的。然而,对于CMS收集器,年轻代GC算法和老年代GC算法是不同的,因此年轻 代GC有两种不同的实现,并且是两个不同的标志。

注意最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC会自动开启。因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC来关掉。

-XX:+CMSConcurrentMTEnabled

当该标志被启用时,并发的CMS阶段将以多线程执行(因此,多个GC线程会与所有的应用程序线程并行工作)。该标志已经默认开启,如果顺序执行更好,这取决于所使用的硬件,多线程执行可以通过-XX:-CMSConcurremntMTEnabled禁用。

 -XX:ConcGCThreads

标志-XX:ConcGCThreads=<value>(早期JVM版本也叫-XX:ParallelCMSThreads)定义并发CMS过程运行时的线程数。比如value=4意味着CMS周期的所有阶段都以4个线程来执行。尽管更多的线程会加快并发CMS过程,但其也会带来额外的同步开销。因此,对于特定的应用程序,应该通过测试来判断增加CMS线程数是否真的能够带来性能的提升。

如果还标志未设置,JVM会根据并行收集器中的-XX:ParallelGCThreads参数的值来计算出默认的并行CMS线程数。该公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此,对于CMS收集器, -XX:ParallelGCThreads标志不仅影响“stop-the-world”垃圾收集阶段,还影响并发阶段。

总之,有不少方法可以配置CMS收集器的多线程执行。正是由于这个原因,建议第一次运行CMS收集器时使用其默认设置, 然后如果需要调优再进行测试。只有在生产系统中测量(或类生产测试系统)发现应用程序的暂停时间的目标没有达到 , 就可以通过这些标志应该进行GC调优。

-XX:CMSInitiatingOccupancyFraction

当堆满之后,并行收集器便开始进行垃圾收集,例如,当没有足够的空间来容纳新分配或提升的对象。对于CMS收集器,长时间等待是不可取的,因为在并 发垃圾收集期间应用持续在运行(并且分配对象)。因此,为了在应用程序使用完内存之前完成垃圾收集周期,CMS收集器要比并行收集器更先启动。

因为不同的应用会有不同对象分配模式,JVM会收集实际的对象分配(和释放)的运行时数据,并且分析这些数据,来决定什么时候启动一次CMS垃圾收集周期。为了引导这一过程, JVM会在一开始执行CMS周期前作一些线索查找。该线索由 -XX:CMSInitiatingOccupancyFraction=& lt;value>来设置,该值代表老年代堆空间的使用率。比如,value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。通 常CMSInitiatingOccupancyFraction的默认值为68(之前很长时间的经历来决定的)。

-XX:+UseCMSInitiatingOccupancyOnly

我们用-XX+UseCMSInitiatingOccupancyOnly标志来 命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期。而是,当该标志被开启时,JVM通过 CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不仅仅是第一次。然而,请记住大多数情况下,JVM比我们自 己能作出更好的垃圾收集决策。因此,只有当我们充足的理由(比如测试)并且对应用程序产生的对象的生命周期有深刻的认知时,才应该使用该标志。

-XX:+CMSClassUnloadingEnabled

相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收。如果希望对永久代进行垃圾回收,可用设置标志-XX:+CMSClassUnloadingEnabled。 在早期JVM版本中,要求设置额外的标志-XX:+CMSPermGenSweepingEnabled。注意,即使没有设置这个标志,一旦永久代耗尽空 间也会尝试进行垃圾回收,但是收集不会是并行的,而再一次进行Full GC。

-XX:+CMSIncrementalMode

该标志将开启CMS收集器的增量模式。增量模式经常暂停CMS过程,以便对应用程序线程作出完全的让步。因此,收集器将花更长的时间完成整个收集周 期。因此,只有通过测试后发现正常CMS周期对应用程序线程干扰太大时,才应该使用增量模式。由于现代服务器有足够的处理器来适应并发的垃圾收集,所以这 种情况发生得很少。

-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

如今,被广泛接受的最佳实践是避免显式地调用GC(所谓的“系统GC”),即在应用程序中调用system.gc()。然而,这个建议是不管使用的 GC算法的,值得一提的是,当使用CMS收集器时,系统GC将是一件很不幸的事,因为它默认会触发一次Full GC。幸运的是,有一种方式可以改变默认设置。标志-XX:+ExplicitGCInvokesConcurrent命令JVM无论什么时候调用系统 GC,都执行CMS GC,而不是Full GC。第二个标志-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保证当有系统GC调用时,永久代 也被包括进CMS垃圾回收的范围内。因此,通过使用这些标志,我们可以防止出现意料之外的”stop-the-world”的系统GC。

-XX:+DisableExplicitGC

然而在这个问题上…这是一个很好提到- XX:+ DisableExplicitGC标志的机会,该标志将告诉JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)。对于我而言,该标志属于默认 的标志集合中,可以安全地定义在每个JVM上运行,而不需要进一步思考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值