长期运行Groovy应用频繁触发Full GC,如何降低STW时间?
如何降低运行大量Groovy脚本的Java应用的Full GC停顿时间?
环境
服务器: 8核CPU, 16GB内存, Docker 操作系统: CentOS6 JDK: Oracle JDK8u191 JVM参数: -server -Xms8192m -Xmx8192m -XX:MaxDirectMemorySize=4g -XX:MaxMetaspaceSize=768m -XX:+UseG1GC -XX:ParallelGCThreads=8 -XX:ConcGCThreads=2 -XX:MaxGCPauseMillis=50 Groovy版本: 4.0.8
已排查内容
- 首次Full GC前,每次Metadata Minor GC后,Metaspace阈值都会小幅上升:
当元数据大小达到约630MB阈值后,每次GC都会变为Full GC。首次Full GC在运行1个月后出现,之后每日频繁触发。每次Full GC后元数据大小回到300MB+,无资源泄漏。示例: 第1次: GC前元数据大小: 502.14MB GC后元数据大小: 359.07MB 第2次: GC前元数据大小: 535.11MB GC后元数据大小: 361.08MB 第3次: GC前元数据大小: 559.96MB GC后元数据大小: 358.70MB ... - 开启JVM的
TraceClassLoading和TraceClassUnloading后,频繁出现数百条如下日志:
尝试通过Arthas dump类时进程崩溃,未成功。[Loaded java.lang.invoke.LambdaForm$MH/1818461913 from java.lang.invoke.LambdaForm] [Loaded java.lang.invoke.LambdaForm$MH/1950338341 from java.lang.invoke.LambdaForm] [Loaded java.lang.invoke.LambdaForm$MH/411925311 from java.lang.invoke.LambdaForm] ... //Metadata GC Threshold ... [Unloading class java.lang.invoke.LambdaForm$MH/1871712942 0x00000007c7ac3828] [Unloading class java.lang.invoke.LambdaForm$MH/55669925 0x00000007c7ac3428] [Unloading class java.lang.invoke.LambdaForm$MH/1781124732 0x00000007c7ac3028] - 将
MaxGCPauseMillis从50ms调整为100ms,元数据阈值仍持续上升,问题未解决。
Metadata GC Threshold日志示例
2025-04-25T08:44:54.824+0800: 1549660.716: [GC pause (Metadata GC Threshold) (young) (initial-mark), 0.0793971 secs] [Parallel Time: 73.7 ms, GC Workers: 8] [GC Worker Start (ms): Min: 1549660734.7, Avg: 1549660734.7, Max: 1549660734.8, Diff: 0.1] [Ext Root Scanning (ms): Min: 47.2, Avg: 47.7, Max: 48.5, Diff: 1.3, Sum: 381.3] [Update RS (ms): Min: 3.4, Avg: 3.5, Max: 3.6, Diff: 0.1, Sum: 28.1] [Processed Buffers: Min: 135, Avg: 155.5, Max: 183, Diff: 48, Sum: 1244] [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.0, Sum: 0.4] [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.3] [Object Copy (ms): Min: 4.3, Avg: 5.1, Max: 5.5, Diff: 1.2, Sum: 41.0] [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.4] [Termination Attempts: Min: 1, Avg: 87.0, Max: 121, Diff: 120, Sum: 696] [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.7] [GC Worker Total (ms): Min: 56.5, Avg: 56.5, Max: 56.6, Diff: 0.2, Sum: 452.2] [GC Worker End (ms): Min: 1549660791.2, Avg: 1549660791.2, Max: 1549660791.3, Diff: 0.1] [Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.2 ms] [Other: 5.4 ms] [Choose CSet: 0.0 ms] [Ref Proc: 3.1 ms] [Ref Enq: 0.0 ms] [Redirty Cards: 0.1 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.3 ms] [Eden: 2272.0M(4896.0M)->0.0B(4896.0M) Survivors: 16.0M->16.0M Heap: 3627.3M(8192.0M)->1358.9M(8192.0M)] [Times: user=0.48 sys=0.00, real=0.08 secs] 2025-04-25T08:44:54.904+0800: 1549660.796: [GC concurrent-root-region-scan-start] 2025-04-25T08:44:54.917+0800: 1549660.809: [GC concurrent-root-region-scan-end, 0.0130581 secs] 2025-04-25T08:44:54.917+0800: 1549660.809: [GC concurrent-mark-start] 2025-04-25T08:44:55.259+0800: 1549661.151: [GC concurrent-mark-end, 0.3418808 secs] 2025-04-25T08:44:55.268+0800: 1549661.160: [GC remark 2025-04-25T08:44:55.268+0800: 1549661.160: [Finalize Marking, 0.0009449 secs] 2025-04-25T08:44:55.269+0800: 1549661.161: [GC ref-proc, 0.0094765 secs] 2025-04-25T08:44:55.279+0800: 1549661.170: [Unloading, 0.4803602 secs], 0.4925154 secs] [Times: user=0.74 sys=0.48, real=0.50 secs]
(暂未获取到Full GC日志)
解决方案
针对Metaspace与Lambda类生成的优化
- 开启Groovy Lambda缓存:添加JVM参数
-Dgroovy.lambda.conversion.cache=true,让Groovy复用Lambda转换生成的类,减少LambdaForm$MH类的频繁加载卸载。 - 升级JDK版本:将Oracle JDK8u191升级到8u301及以上版本,这些版本优化了Metaspace的GC阈值调整逻辑和类卸载机制,能有效缓解阈值持续上升的问题。
- 调整Metaspace参数:
- 增大
-XX:MaxMetaspaceSize至1024m,延长首次触发Full GC的周期; - 设置
-XX:MetaspaceSize=512m固定初始阈值,避免JVM动态上调阈值; - 确认
-XX:+UseCompressedClassPointers已开启(JDK8默认开启),减少Metaspace内存占用。
- 增大
G1GC参数优化
- 提升并发GC线程数:将
-XX:ConcGCThreads=2调整为-XX:ConcGCThreads=4,匹配8核CPU的资源,加快并发标记和类卸载速度,缩短Remark阶段的STW时间。 - 优化Metaspace GC触发逻辑:添加
-XX:+CMSClassUnloadingEnabled和-XX:+ExplicitGCInvokesConcurrent参数,确保Metaspace GC使用并发模式,避免触发Full GC。 - 调整停顿目标:将
-XX:MaxGCPauseMillis=50调整为-XX:MaxGCPauseMillis=200,降低G1的停顿压力,减少Young GC频率,间接减缓Metaspace增长速度。
Groovy脚本执行优化
- 复用GroovyClassLoader:避免每次执行脚本都创建新的类加载器,统一管理类加载器实例,减少类的重复加载和卸载。
- 启用Groovy类缓存:添加JVM参数
-Dgroovy.classpath.cache=true,开启Groovy的类路径缓存,对重复执行的脚本复用已生成的类。 - 预编译Groovy脚本:将频繁执行的脚本预编译为
.class文件,直接加载预编译类,避免运行时动态编译生成新类,从根源降低Metaspace压力。
内容的提问来源于stack exchange,提问作者atealxt
相关产品推荐
相关产品推荐

