Young GC 500ms到50ms的优化

本文分享了在备战双十一期间,针对JVM频繁YoungGC及耗时问题的优化过程。通过对MQ消费应用与WEB应用资源隔离,手动设置GC线程数量,启用并行回收等策略,显著降低了GC耗时,从500ms降至约60ms。

问题描述:在进行双十一备战的过程中,发现JVMyoung GC的频次很高,同时一次Young Gc的耗时在500ms左右,FullGC的在1-2天触发一次

JVM原配置:-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly  -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

优化方向:关注Young GC ,FullGC触发次数可不考虑

(1)、考虑到这个应用是一个WEB应用,同时也是MQ的消费端,应该第一个原因就是MQ的消费会导致大量的临时对象产生,YOUNG GC频繁,同时在接受商品上下架(全站)、收银台对账消息,每个都在3000/TPS的量,峰值能达到10000+。所以第一个优化思路,是将MQ消费应用,与WEB应用分离,减少之间的影响。

优化完,MQ消费应用YOUNGGC频次同之前,WEB应用YOUNGGC2分钟一次,每次GC耗时:50ms

总结:资源隔离,保护线上稀有资源,是针对系统的优化需要关注的点,此时针对于YOUNG GC已到达本身的系统要求,针对于MQ消费者应用,YOUNGGC频次高一些,对于消费应用的延迟来说还是可接受的。但是还是想搞清楚时间消耗在哪里?

(2)YoungGC时间消耗:

   1.Root Scanning        

   2.Object Copy  

GC roots 包含以下引用:                         

    1.所有java线程以及线程栈帧里指向GC堆里的对象的引用;                            

    2.JNI Local & Global;                                                                                           

    3.由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的;      

    4.stack local Java方法的local变量或参数;                                              

    5.其他,包含monitor & finalizable & native stack

排查方法,打印各种日志。

-XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log 打印日志,,

XX:+PrintReferenceGC 打印Reference回收消耗时长

-XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 打印安全点时间消耗

-XX:+PrintTenuringDistributio 打印年龄分布(注意动态年龄计算)

查看日志后发现,这几个的时间跟500ms都不是一个量级的,所以根本原因不在这里。

后面考虑到没有设置GC回收线程数量,通过DUMP、JCMD命令可以发现GC线程数量远远超过服务器核数,(4C8G服务器配置)

    ①如果用户显示指定了ParallelGCThreads,则使用用户指定的值。
    ②否则,需要根据实际的CPU所能够支持的线程数来计算ParallelGCThreads的值,计算方法见步骤③和步骤④。
    ③如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8,是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
    ④如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
    比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。

  ParallelGCThreads= 8 + (N - 8) * 5 / 8

手动设置,ParallelGCThreads= 4

YoungGC耗时变成75ms左右。

此时大部分GC耗时已经解决,继续优化,

1.发现安全点时间耗时都是几ms,故排除这个优化点

2.finalReference耗时在 20-40ms左右,开启-XX:+ParallelRefProcEnabled,并行回收,优化到60ms左右

3.打印年龄代分布,可以看到age1  age2差距比较大,应该大多在第一次younggc就可以回收的对象,在eden区又copy到 s0区,减少,copy,可以稍微增加xmn设置大小,(测试了1G 1.5G 2G)根据不同系统可能不同,最终选择2G在频次和耗时之间折中选择。

备注:

AGE年龄优化分析:

Eden移动到Survivor情况:假设机器2 Eden区域是 机器1 的两倍大,其他条件都保持不变。就一般情况来说(Survivor区域中存活的对象比Eden少很多,比如1%),那么机器1 young gc的频率是 机器2 young gc 频率的2倍;那么假设机器1在T时间内GC一次,在GC之后由Eden区域晋升到Survivor的大小为10M(即age=1),那么机器2在2T时间之后发生GC,1T-2T之间生成的对象和机器1类似,GC之后有10M进入Survivor区域,但是0T-1T内最多会剩余10M内存可能会进入到Survivor,但是在经历1T-2T时间之后也有可能导致object已经不存活,如何判断这部分对象有没有存活呢,在机器1在2T的时间点要又要进行一次young gc,那么在0T-1T之前存活的对象也就是age=1的对象将会再次会经历一次young gc,便是了age=2,所有看age=2的年龄段剩余多少就可以了。机器2一次GC之后,由Eden区域进入到Survivor区域中的大约等于10M+机器1中Survivor中(age=2)也就是机器1:age1+age2中的object对象。

-Xmn2048M -XX:ParallelGCThreads=4  -XX:+ParallelRefProcEnabled  -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=15 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log -XX:+PrintReferenceGC -XX:+PrintTenuringDistribution -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

<think>嗯,用户询问Elasticsearch中2msGC耗时是否正常。这个问题需要结合JVM垃圾回收机制和Elasticsearch的实践来分析。 首先回忆GC的基本原理。JVM的垃圾回收暂停时间与堆大小、对象分配速率、GC算法选择等因素相关。Elasticsearch默认使用G1收集器,其设计目标就是在可控的停顿时间内处理大内存堆。 注意到用户提到的2ms这个数值。从绝对数值看确实很短,但判断正常与否需要更多上下文。参考行业实践,通常建议关注几个关键点: 1. 停顿频率:如果每2秒就发生一次2msGC,累积影响可能比每分钟一次50msGC更大 2. 堆内存占比:GC日志中的"GC overhead"指标更重要,健康集群应保持在10%以下 3. 分代行为:年轻代GC本应短暂,而老年代GC耗时稍长也属正常 用户可能忽略了查看完整GC日志的重要性。建议用户通过以下命令获取更全面的数据: ```bash GET /_nodes/stats/jvm?filter_path=**.gc ``` 典型需要关注的字段包括: - `collectors.young.collection_count`:年轻代回收次数 - `collectors.old.collection_time_in_millis`:老年代总耗时 突然想到用户可能是看到监控面板的孤立数据产生疑问。需要强调:单次2msGC暂停完全正常,特别是年轻代回收。真正需要警惕的是老年代频繁Full GC或停顿时间突然增长10倍以上。 结合曾经处理过的案例,某客户集群虽然单次GC仅3ms,但因每秒触发20次,导致实际GC开销超过15%。这种情况就需要调整`-XX:G1NewSizePercent`参数了。 最后要提醒用户:不同版本ES的GC表现也有差异。7.x版本后G1优化明显,而6.x版本可能CMS收集器表现更好。如果用户仍在使用旧版,这个2ms的数据反而可能异常——因为CMS的初始标记阶段通常需要5ms以上。</think>根据Elasticsearch的JVM垃圾回收机制和行业实践,**2ms的垃圾回收耗时属于非常优秀的正常范围**,通常无需担忧。以下是详细分析: --- ### **评估标准与关键因素** 1. **GC暂停类型** - **年轻代(Young GC)**:通常耗时1-10ms,2ms是**理想值**(如ParNew/CMS或G1的年轻代回收) - **老年代(Full GC)**:若2ms属于老年代回收,则可能异常(通常Full GC需几十至几百ms) *需通过GC日志确认类型*: ```bash # 检查GC日志(ES默认开启) grep "GC" /var/log/elasticsearch/gc.log ``` 2. **堆内存大小影响** - **小堆(<4GB)**:2ms GC常见 - **大堆(>32GB)**:2ms较罕见,可能因低负载或优化得当即**正常** *参考指标*: ```java -XX:G1NewSizePercent=5 // 年轻代最小占比(G1收集器) -XX:G1MaxNewSizePercent=30 // 年轻代最大占比 ``` 3. **健康阈值参考** | **指标** | 正常范围 | 需关注阈值 | |-------------------------|---------------|------------| | 单次Young GC耗时 | < 10ms | > 50ms | | 单次Full GC耗时 | < 1s | > 2s | | **GC总耗时占比** | **< 10%** | > 30% | | GC频率(Young GC) | < 1次/秒 | > 10次/秒 | --- ### **操作建议:深度验证** 1. **检查GC总开销** 通过Elasticsearch API获取GC累积时间: ```bash GET /_nodes/stats/jvm?filter_path=**.gc ``` **关键输出**: ```json "collectors": { "young": { "collection_count": 120, // 年轻代GC次数 "collection_time_in_millis": 240 // 总耗时(ms) }, "old": { "collection_count": 2, "collection_time_in_millis": 4000 } } ``` **计算GC开销**: $$ \text{GC开销} = \frac{\text{总GC耗时}}{\text{节点运行时间}} \times 100\% $$ **若结果 < 10% 则正常**。 2. **分析GC日志模式** - **频繁短时GC**(如每5秒1次2ms):可能因对象分配过快,需优化写入批处理 - **偶发长时GC**(如1小时1次500ms):通常可接受 3. **关联系统指标** | 指标 | 正常表现 | |---------------------|------------------------------| | CPU使用率 | GC期间短暂峰值(< 80%) | | 堆内存使用率 | 稳定波动,无持续90%+ | | 查询延迟 | 无周期性飙升 | --- ### **结论** - **若2ms为年轻代GC**:**完全正常**,表明JVM调优良好。 - **若2ms为老年代GC**:需排查是否误判(如日志截断)或低负载场景下的特殊案例。 - **整体建议**: ✅ 持续监控GC频率和总开销(推荐工具:Elasticsearch Monitoring / GCViewer) ✅ 当GC耗时占比 > 15% 或 Full GC > 1次/天时介入优化[^1][^2]。 --- ### 相关问题 1. 如何优化Elasticsearch的JVM设置以减少GC停顿? 2. Elasticsearch的堆内存应该设置为多大?有哪些配置陷阱? 3. 哪些业务场景会导致Elasticsearch频繁Full GC?如何预防? [^1]: Elasticsearch的刷新机制和段合并过程会影响内存对象生成速率,间接影响GC行为。 [^2]: JVM参数如`-XX:MaxGCPauseMillis`需谨慎设置,避免牺牲吞吐量换取低延迟。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值