关于CMS的两个常用参数的解读

最近观看一个视频课程,关于CMS两个常用参数的讲解,提及“堆内存碎片的整理发生在N次CMS之后”;发现该结论与自己理解的不一致,遂翻查资料求证,现整理如下,备查。

Backgroud CMS
可能更多人只知道CMS,而不知道Backgroud CMS。事实上我们说的CMS,即包含了5个阶段的CMS,就是Background CMS:

  1. 初始标记阶段
  2. 并发标记阶段
  3. 重新标记阶段
  4. 并发清除阶段
  5. 并发重置阶段

说明:
初始化标记阶段是串行的,这是JDK7的行为。JDK8以后默认是并行的,可以通过参数-XX:+CMSParallelInitialMarkEnabled控制。
CMS有两个阶段是完全STW(Stop The World)的,即初始化标记和最终标记(重新标记)。
其他阶段都是并发的,所以CMS被称为Concurrent Mark&Sweep,但是我认为前面还需要加个Mostly才是最贴切,即CMS是一个Mostly Concurrent Mark and Sweep Garbage Collector,因为它还没办法做到完全并发。

不只是CMS,就是G1,以及JDK11的ZGC都没有做到完全的并发。就目前笔者了解到的所有GC中,只有Azul的C4是完全并发的。

为什么有个Background关键词?我们都知道配置CMS垃圾回收的话,有两个重要参数:-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly,这两个参数表示只有在Old区占了75%的内存时才满足触发CMS的条件。注意这只是满足触发CMS GC的条件。至于什么时候真正触发CMS GC,由一个后台扫描线程决定。CMSThread默认2秒钟扫描一次,判断是否需要触发CMS,这个参数可以更改这个扫描时间间隔,例如-XX:CMSWaitDuration=5000,此外可以通过jstack日志看到这个线程:

"Concurrent Mark-Sweep GC Thread" os_prio=2 tid=0x000000001870f800 nid=0x0f4 waiting on condition

Foregroud CMS
这个名词第一次听笨神说的(公众号:你假笨)。当然笨神也不是随便自己捏造一个名词出来,这个名词来自于openjdk源码,参考concurrentMarkSweepGeneration.cpp:

void CMSCollector::collect_in_foreground(bool clear_all_soft_refs, GCCause::Cause cause) {
    case Resizing: {
        // nothing to be done in this state. 即这个阶段啥都没做
        _collectorState = Resetting;
        break;
    }  
    case Precleaning:
        // 预清理啥都没干
    case AbortablePreclean:
        // Elide(省略,取消的意思,相当于这个阶段也啥都没做) the preclean phase
        _collectorState = FinalMarking;
        break;
    default:
        ShouldNotReachHere();
}

它发生的场景,比如业务线程请求分配内存,但是内存不够了,于是可能触发一次CMS GC,这个过程就必须要等待内存分配成功后业务线程才能继续往下面走,因此整个过程必须STW,所以这种CMS GC整个过程都是STW,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,通过上面的源码可知,这些省下来的阶段主要是并行阶段:Precleaning、AbortablePreclean,Resizing。但不管怎么说如果走了类似foreground这种CMS GC,那么整个过程业务线程都是不可用的,效率会影响挺大。

这事实上就是发生了FullGC,由这段的分析可知FullGC相比CMS Backgroud collect模式差距还是非常大的。

MSC
MSC的全称是Mark Sweep Compact,即标记-清理-压缩,MSC是一种算法,请注意Compact,即它会压缩整理堆,这一点很重要。

这是foreground CMS在特定情况下才会采用的一种垃圾回收算法。为什么这么说了,这里需要介绍两个参数,这两个参数表示多少次FullGC后采用MSC算法压缩堆内存,0表示每次FullGC后都会压缩,同时0也是默认值:

-XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction=0

配置-XX:+UseCMSCompactAtFullCollection(默认)前提下,如果CMSFullGCsBeforeCompaction=0,那么每次foreground
CMS后都会采用MSC算法压缩堆内存;如果CMSFullGCsBeforeCompaction=3,那么每3次foreground
CMS后才会有1次采用MSC算法压缩堆内存。

碎片问题也是CMS采用的标记清理算法最让人诟病的地方:Backgroud CMS采用的标记清理算法会导致内存碎片问题,从而埋下发生FullGC导致长时间STW的隐患。

所以如果触发了FullGC,无论是否会采用MSC算法压缩堆,那都是ParNew+CMS组合非常糟糕的情况。因为这个时候并发模式已经搞不定了,而且整个过程单线程,完全STW,可能会压缩堆(是否压缩堆通过上面两个参数控制),真的不能再糟糕了!想象如果这时候业务量比较大,由于FullGC导致服务完全暂停几秒钟,甚至上10秒,对用户体验影响得多大。

总结
堆的内存碎片整理发生在N次FullGC之后,而非N次CMS之后。

参考引用:https://www.jianshu.com/p/be5389ca93f7

### Java 启动参数及其分类 Java 启动参数可以分为三类:标准参数、非标准参数(以 `-X` 开头)和 JVM 选项(以 `-XX` 开头)。这些参数用于控制 Java 虚拟机(JVM)的行为,包括内存管理、垃圾回收、性能优化等方面。 #### 标准参数 标准参数Java 官方提供,并保证在所有 JVM 实现中可用,且具有向后兼容性。例如: - `-version`:显示 Java 运行环境的版本信息。 - `-help`:显示标准参数的帮助信息。 - `-jar`:运行打包为 JAR 文件的应用程序。 示例: ```bash java -version ``` #### 非标准参数(以 `-X` 开头) 非标准参数常用JVM 的特定实现,并不保证在所有 JVM 中都可用,也不保证向后兼容。可以使用 `java -X` 命令查看当前 JVM 支持的非标准参数常用非标准参数包括: - `-Xms<size>`:设置 JVM 堆的初始大小。 - `-Xmx<size>`:设置 JVM 堆的最大大小。 - `-Xss<size>`:设置每个线程的堆栈大小。 示例: ```bash java -Xms512m -Xmx1024m MyApplication ``` #### JVM 选项(以 `-XX` 开头) JVM 选项用于更细粒度地控制 JVM 的行为,分为布尔类型和值类型两种形式: - 布尔类型:使用 `-XX:+<option>` 启用某个特性,使用 `-XX:-<option>` 禁用某个特性。 - 值类型:使用 `-XX:<option>=<value>` 设置特定值。 常用 JVM 选项包括: - `-XX:+UseSerialGC`:启用 Serial 垃圾回收器。 - `-XX:+UseParallelGC`:启用 Parallel Scavenge 垃圾回收器。 - `-XX:+UseConcMarkSweepGC`:启用 CMS 垃圾回收器(适用于 JDK 8 及更早版本)。 - `-XX:+UseZGC`:启用 ZGC 垃圾回收器(适用于 JDK 11 及更高版本)。 - `-XX:MaxMetaspaceSize=<size>`:设置元空间的最大大小。 - `-XX:+HeapDumpOnOutOfMemoryError`:在发生内存溢出时生成堆转储文件。 - `-XX:HeapDumpPath=<path>`:指定堆转储文件的保存路径。 示例: ```bash java -XX:+UseZGC -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dump.hprof MyApplication ``` ### Java 启动参数配置示例 以下是一个适用于 JDK 17 的 JVM 参数配置示例,展示了如何优化性能和调试信息: ```bash java \ --add-opens=java.base/java.lang=ALL-UNNAMED \ -Xms1500m -Xmx1500m \ -XX:ReservedCodeCacheSize=256m \ -XX:InitialCodeCacheSize=256m \ -XX:+UnlockExperimentalVMOptions \ -XX:+UseZGC \ -XX:ConcGCThreads=1 \ -XX:ParallelGCThreads=2 \ -XX:ZCollectionInterval=30 \ -XX:ZAllocationSpikeTolerance=5 \ -XX:+UnlockDiagnosticVMOptions \ -XX:-ZProactive \ -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/gc-%t.log:time,tid,tags:filecount=5,filesize=50m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/opt/errorDump.hprof \ MyApplication ``` ### 参数解读 - `--add-opens`:允许反射访问某些内部类。 - `-Xms` 和 `-Xmx`:设置堆的初始和最大大小为 1500MB。 - `-XX:ReservedCodeCacheSize` 和 `-XX:InitialCodeCacheSize`:设置代码缓存区的大小为 256MB。 - `-XX:+UnlockExperimentalVMOptions`:启用实验性 JVM 选项。 - `-XX:+UseZGC`:启用 ZGC 垃圾回收器。 - `-XX:ConcGCThreads` 和 `-XX:ParallelGCThreads`:设置并发和并行垃圾回收线程数。 - `-XX:ZCollectionInterval`:设置 ZGC 的收集间隔为 30 秒。 - `-XX:ZAllocationSpikeTolerance`:设置 ZGC 的内存分配波动容忍度。 - `-XX:+UnlockDiagnosticVMOptions` 和 `-XX:-ZProactive`:启用诊断选项并禁用 ZGC 的主动垃圾回收。 - `-Xlog`:设置日志输出格式和路径,记录 GC、类加载等信息。 - `-XX:+HeapDumpOnOutOfMemoryError` 和 `-XX:HeapDumpPath`:在内存溢出时生成堆转储文件并指定路径。 合理配置 Java 启动参数可以显著提高 Java 应用程序的性能和稳定性[^2]。通过理解和使用这些参数,开发者能够为其 Java 应用程序提供更好的环境设置和资源管理[^1]。 ### 示例代码:Java 启动参数测试 以下是一个简单的 Java 程序,用于测试启动参数的效果: ```java public class MemoryTest { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); System.out.println("Max memory: " + (maxMemory / (1024 * 1024)) + " MB"); } } ``` 运行时指定堆内存大小: ```bash java -Xms256m -Xmx512m MemoryTest ``` 输出结果: ``` Max memory: 512 MB ``` ### 总结 Java 启动参数Java 程序运行中一个不可或缺的部分。通过合理配置这些参数,开发者可以更好地掌控程序的运行环境,提高性能和稳定性。理解并使用这些参数可以帮助开发者更好地应对不同的实际应用场景[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值