参考文章:
并发编程网:http://ifeve.com/useful-jvm-flags-part-4-heap-tuning/
一、参数分类
HotSpot JVM 提供了三类参数。
- 第一类包括了标准参数。顾名思义,标准参数中包括功能和输出的参数都是很稳定的,很可能在将来的 JVM 版本中不会改变。你可以用 java 命令(或者是用 java -help)检索出所有标准参数。我们在第一部分中已经见到过一些标准参数,例如:-server。
- 第二类是 X 参数,非标准化的参数在将来的版本中可能会改变。所有的这类参数都以 - X 开始,并且可以用 java -X 来检索。注意,不能保证所有参数都可以被检索出来,其中就没有 - Xcomp。
- 第三类是包含 XX 参数(到目前为止最多的),它们同样不是标准的,甚至很长一段时间内不被列出来(最近,这种情况有改变 ,我们将在本系列的第三部分中讨论它们)。然而,在实际情况中 X 参数和 XX 参数并没有什么不同。X 参数的功能是十分稳定的,然而很多 XX 参数仍在实验当中(主要是 JVM 的开发者用于 debugging 和调优 JVM 自身的实现)。值的一读的介绍非标准参数的文档 HotSpot JVM documentation,其中明确的指出 XX 参数不应该在不了解的情况下使用。这是真的,并且我认为这个建议同样适用于 X 参数(同样一些标准参数也是)。不管类别是什么,在使用参数之前应该先了解它可能产生的影响。
用一句话来说明 XX 参数的语法。所有的 XX 参数都以”-XX:” 开始,但是随后的语法不同,取决于参数的类型:
- 对于布尔类型的参数,我们有”+” 或”-“,然后才设置 JVM 选项的实际名称。例如,-XX:+ 用于激活 选项,而 - XX:- 用于注销选项。
- 对于需要非布尔值的参数,如 string 或者 integer,我们先写参数的名称,后面加上”=”,最后赋值。例如, -XX:= 给 赋值 。
现在让我们来看看 JIT 编译方面的一些 XX 参数:-XX:+PrintCompilation and -XX:+CITime
-XX:+PrintCompilation
$ java -server -XX:+PrintCompilation Benchmark
1 java.lang.String::hashCode (64 bytes)
2 java.lang.AbstractStringBuilder::stringSizeOfInt (21 bytes)
3 java.lang.Integer::getChars (131 bytes)
4 java.lang.Object::<init> (1 bytes)
--- n java.lang.System::arraycopy (static)
5 java.util.HashMap::indexFor (6 bytes)
6 java.lang.Math::min (11 bytes)
7 java.lang.String::getChars (66 bytes)
8 java.lang.AbstractStringBuilder::append (60 bytes)
9 java.lang.String::<init> (72 bytes)
10 java.util.Arrays::copyOfRange (63 bytes)
11 java.lang.StringBuilder::append (8 bytes)
12 java.lang.AbstractStringBuilder::<init> (12 bytes)
13 java.lang.StringBuilder::toString (17 bytes)
14 java.lang.StringBuilder::<init> (18 bytes)
15 java.lang.StringBuilder::append (8 bytes)
[...]
29 java.util.regex.Matcher::reset (83 bytes)
每当一个方法被编译,就输出一行 - XX:+PrintCompilation。
每行都包含顺序号(唯一的编译任务 ID)和已编译方法的名称和大小。因此,顺序号 1,代表编译 String 类中的 hashCode 方法到原生代码的信息。
根据方法的类型和编译任务打印额外的信息。例如,本地的包装方法前方会有”n” 参数,像上面的 System::arraycopy 一样。注意这样的方法不会包含顺序号和方法占用的大小,因为它不需要编译为本地代码。
同样可以看到被重复编译的方法,例如 StringBuilder::append 顺序号为 11 和 15。输出在顺序号 29 时停止 ,这表明在这个 Java 应用运行时总共需要编译 29 个方法。
-XX:+CITime
设置 - XX:+CITime,我们可以在 JVM 关闭时得到各种编译的统计信息,例如:
Accumulated compiler times (for compiled methods only) ------------------------------------------------ Total compilation time : 0.165 s Standard compilation : 0.165 s, Average : 0.000 On stack replacement : 0.000 s, Average : -1.#IO Detailed C1 Timings Setup time: 0.000 s ( 0.0%) Build IR: 0.080 s (49.9%) Optimize: 0.008 s ( 4.8%) RCE: 0.001 s ( 0.6%) Emit LIR: 0.059 s (36.6%) LIR Gen: 0.011 s ( 6.8%) Linear Scan: 0.047 s (29.5%) LIR Schedule: 0.000 s ( 0.0%) Code Emission: 0.015 s ( 9.2%) Code Installation: 0.007 s ( 4.3%) Instruction Nodes: 60882 nodes Total compiled methods : 667 methods Standard compilation : 667 methods On stack replacement : 0 methods Total compiled bytecodes : 95744 bytes Standard compilation : 95744 bytes On stack replacement : 0 bytes Average compilation speed: 581621 bytes/s nmethod code size : 474048 bytes nmethod total size : 1057916 bytes
二、打印所有-XX参数值
-XX:+PrintFlagsFinal and -XX:+PrintFlagsInitial
$ java -XX:+PrintFlagsFinal Benchmark [Global flags] uintx AdaptivePermSizeWeight = 20 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product}[...] uintx YoungGenerationSizeSupplementDecay = 8 {product} uintx YoungPLABSize = 4096 {product} bool ZeroTLAB = false {product} intx hashCode = 0 {product}
表格的每一行包括五列,来表示一个 XX 参数。第一列表示参数的数据类型,第二列是名称,第四列为值,第五列是参数的类别。第三列”=” 表示第四列是参数的默认值,而“:=” 表明了参数被用户或者 JVM 赋值了。
我们也能指定参数 -XX:+UnlockExperimentalVMOptions 和 -XX:+UnlockDiagnosticVMOptions;来解锁任何额外的隐藏参数,例如:
$ java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark
这篇文章解释了第5列参数:http://q-redux.blogspot.jp/2011/01/inspecting-hotspot-jvm-options.html
-XX:+PrintCommandLineFlags
这个参数让 JVM 打印出那些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值。
换句话说,它列举出 -XX:+PrintFlagsFinal的结果中第三列有“:=”的参数。以这种方式,我们可以用 - XX:+PrintCommandLineFlags 作为快捷方式来查看修改过的参数。看下面的例子。
$ java -server -XX:+PrintCommandLineFlags Benchmark -XX:InitialHeapSize=57505088 -XX:MaxHeapSize=920081408 -XX:ParallelGCThreads=4 -XX:+PrintCommandLineFlags -XX:+UseParallelGC
现在如果我们每次启动 java 程序的时候设置 -XX:+PrintCommandLineFlags 并且输出到日志文件上,这样会记录下我们设置的 JVM 参数对应用程序性能的影响。
建议 –XX:+PrintCommandLineFlags 这个参数应该总是设置在 JVM 启动的配置项里。因为你从不知道你什么时候会需要这些信息。
三、内存相关参数
-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)
堆大小设置,建议相同以获取稳定的堆环境
java -Xms2g -Xmx2g MyApp
注意:如使用 -XX:+PrintCommandLineFlags 参数或者通过 JMX 查询,你应该寻找 “InitialHeapSize” 和 “InitialHeapSize” 标志而不是 “Xms” 和 “Xmx”。
-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath
- 我们可以通过设置 -XX:+HeapDumpOnOutOfMemoryError 让 JVM 在发生内存溢出时自动的生成堆内存快照。
- 有了这个参数,当我们不得不面对内存溢出异常的时候会节约大量的时间。默认情况下,堆内存快照会保存在 JVM 的启动目录下名为 java_pid.hprof 的文件里(在这里 就是 JVM 进程的进程号)。
- 也可以通过设置 - XX:HeapDumpPath= 来改变默认的堆内存快照生成路径, 可以是相对或者绝对路径。
注意:堆内存快照文件有可能很庞大,特别是当内存溢出错误发生的时候。因此,我们推荐将堆内存快照生成路径指定到一个拥有足够磁盘空间的地方。
-XX:OnOutOfMemoryError
当内存溢发生时,我们甚至可以可以执行一些指令,比如发个 E-mail 通知管理员或者执行一些清理工作。通过 - XX:OnOutOfMemoryError 这个参数我们可以做到这一点,这个参数可以接受一串指令和它们的参数。在这里,我们将不会深入它的细节,但我们提供了它的一个例子。在下面的例子中,当内存溢出错误发生的时候,我们会将堆内存快照写到 /tmp/heapdump.hprof 文件并且在 JVM 的运行目录执行脚本 cleanup.sh
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError="sh ~/cleanup.sh" MyApp
-XX:PermSize and -XX:MaxPermSize
永久代在堆内存中是一块独立的区域,它包含了所有JVM加载的类的对象表示。为了成功运行应用程序,JVM会加载很多类(因为它们依赖于大量的第三方库,而这又依赖于更多的库并且需要从里面将类加载进来)这就需要增加永久代的大小。我们可以使用-XX:PermSize 和-XX:MaxPermSize 来达到这个目的。其中-XX:MaxPermSize 用于设置永久代大小的最大值,-XX:PermSize 用于设置永久代初始大小。下面是一个简单的例子:
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
请注意,这里设置的永久代大小并不会被包括在使用参数-XX:MaxHeapSize 设置的堆内存大小中。也就是说,通过-XX:MaxPermSize设置的永久代内存可能会需要由参数-XX:MaxHeapSize 设置的堆内存以外的更多的一些堆内存。
-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize
JVM一个有趣的,但往往被忽视的内存区域是“代码缓存”,它是用来存储已编译方法生成的本地代码。
代码缓存确实很少引起性能问题,但是一旦发生其影响可能是毁灭性的。如果代码缓存被占满,JVM会打印出一条警告消息,并切换到interpreted-only 模式:JIT编译器被停用,字节码将不再会被编译成机器码。
因此,应用程序将继续运行,但运行速度会降低一个数量级,直到有人注意到这个问题。就像其他内存区域一样,我们可以自定义代码缓存的大小。相关的参数是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它们的参数和上面介绍的参数一样,都是字节值。
-XX:+UseCodeCacheFlushing
如果代码缓存不断增长,例如,因为热部署引起的内存泄漏,那么提高代码的缓存大小只会延缓其发生溢出。为了避免这种情况的发生,我们可以尝试一个有趣的新参数:当代码缓存被填满时让JVM放弃一些编译代码。通过使用-XX:+UseCodeCacheFlushing 这个参数,我们至少可以避免当代码缓存被填满的时候JVM切换到interpreted-only 模式。不过,我仍建议尽快解决代码缓存问题发生的根本原因,如找出内存泄漏并修复它。
四、新生代垃圾回收
-Xmn
可以使用-Xmn来设定新生代的内存大小,官方建议是整个堆得3/8。
在较老的版本中,还有2个参数-XX:NewSize and -XX:MaxNewSize,而-Xmn则是直接设置一个稳定的新生代大小。
-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 类似,作用于新生代内部区域。默认Eden:S0:S1=8:1:1
-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
每次新生代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
第一行,表示幸存区75m左右,threshold15(max15),表示老年代阈值是15,最大值也是15
age 1: 19321624bytes ,表示年龄为1是的共有19M左右
age 2:79376 bytes, 表示年龄的共有73KB左右, 后面的total,表示<=当前年龄的总字节数
通过输出年龄分布,可以更好理解GC
-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio
很好理解,设置晋升到老年代的对象年龄阈值
-XX:+NeverTenure and -XX:+AlwaysTenure
这2个参数很极端,不建议使用,除非某些特殊的测试场景。
- 设置参数 -XX:+NeverTenure,对象永远不会晋升到老年代。当我们确定不需要老年代时,可以这样设置。这样设置风险很大, 并且会浪费至少一半的堆内存。
- 相反设置参数 -XX:+AlwaysTenure,表示没有幸存区,所有对象在第一次 GC 时,会晋升到老年代。
五、吞吐量收集相关参数(Parallel Scavenge)
在实践中我们发现对于大多数的应用领域,评估一个垃圾收集 (GC) 算法如何根据如下两个标准:
- 吞吐量越高算法越好
- 暂停时间越短算法越好
首先让我们来明确垃圾收集 (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 毫秒。
-XX:+UseSerialGC
指定新生代串行收集器和老年代使用串行收集器
-XX:+UseParallelGC
使用Parallel Scavenge+Serail Old(PS MarkSweep)的收集器进行回收
-XX:+UseParallelOldGC
使用Parallel Scavenge+Parallel Old进行回收,跟上面相比,就是让老年代的收集器变为并行的
-XX:ParallelGCThreads
通过 - XX:ParallelGCThreads= 我们可以指定并行垃圾收集的线程数量。 例如,-XX:ParallelGCThreads=6 表示每次并行垃圾收集将有 6 个线程执行。
如果不明确设置该标志,虚拟机将使用基于可用 (虚拟) 处理器数量计算的默认值。 决定因素是由 Java Runtime。availableProcessors() 方法的返回值 N,如果 N<=8,并行垃圾收集器将使用 N 个垃圾收集线程,如果 N>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= 我们告诉 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 收集器是本系列下一部分的主题。
六、CMS收集器相关参数
前面已经对CMS进行了详细的描述:http://www.cnblogs.com/carl10086/p/6081034.html
CMS是一种真正意义上的并发收集器,让收集器可以主动给运用程序进行让步。下面对CMS收集器用到的参数进行简要说明:
-XX:+UseConcMarkSweepGC
该标志首先是激活 CMS 收集器。默认 HotSpot JVM 使用的是并行收集器。
-XX:UseParNewGC
当使用 CMS 收集器时,该标志激活年轻代使用多线程并行执行垃圾回收。这令人很惊讶,我们不能简单在并行收集器中重用 - XX:UserParNewGC 标志,因为概念上年轻代用的算法是一样的。然而,对于 CMS 收集器,年轻代 GC 算法和老年代 GC 算法是不同的,因此年轻代 GC 有两种不同的实现,并且是两个不同的标志。
注意,当使用 -XX:+UseConcMarkSweepGC 时,-XX:UseParNewGC 会自动开启。因此,如果年轻代的并行 GC 不想开启,可以通过设置 -XX:-UseParNewGC 来关掉。
-XX:+CMSConcurrentMTEnabled
当该标志被启用时,并发的 CMS 阶段将以多线程执行 (因此,多个 GC 线程会与所有的应用程序线程并行工作)。该标志已经默认开启,如果顺序执行更好,这取决于所使用的硬件,多线程执行可以通过 - XX:-CMSConcurremntMTEnabled 禁用。
-XX:ConcGCThreads
标志 - XX:ConcGCThreads=(早期 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=来设置,该值代表老年代堆空间的使用率。比如,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 上运行,而不需要进一步思考。