前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >YGC导致CPU负载过高的排查与解决

YGC导致CPU负载过高的排查与解决

作者头像
叔牙
修改2021-12-10 09:53:11
3.8K0
修改2021-12-10 09:53:11
举报

概述

在发现XXX系统的负载过高后确定解决方案,本文记录了整个过程。先说结论:

  1. jdk 1.8 中使用 CMS 收集器,UseAdaptiveSizePolicy 参数会被设置为 false,导致 young 区和 old 区大小不会动态调整
  2. jdk 1.8 中使用 CMS 收集器,默认的 newRatio=2 不会生效,需要显示配置此参数或者配置 young 大小。否则按照 cpu 核心数量计算 young 大小:64M * cpu 核心数 * 13 / 10
  3. 批量任务每次任务量过大,短时间内创建大量对象,导致 jvm 疯狂的 young gc
  4. 频繁 young gc 导致 CPU 使用率过高,系统

一、现象

在报警群里看到 XXX 服务所在的服务器负载很高, 4 核 16G 的配置,CPU 使用率 >90%

二、排查过程

查看 GC 情况

1.幸存区使用率接近 100% 2.频繁 young gc,每秒钟都有

使用 arthas 查看 CPU 占用情况

1.定时拉取任务占用了 95% 的 CPU 2.新生代大小 332MB

初步判断为新生代太小,而定时任务创建大量对象而且任务有堆积,对象不能被释放,从而导致幸存区使用率过高,发生频繁的 gc。

为什么新生代是 332.8MB

在做出调整之前要找到 newRatio 没生效的原因,为什么 8G 的堆内存,新生代只有 332MB

登上服务器查看服务启动时的参数配置:

java -server 
-Xmx2048m 
-Xms2048m 
-XX:+UseConcMarkSweepGC 
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 
-XX:+CMSScavengeBeforeRemark 
-XX:+UseCMSInitiatingOccupancyOnly 
-XX:CMSInitiatingOccupancyFraction=70 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps 
-XX:+PrintGCDetails 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/home/admin/logs/tracking-center 
-Xloggc:/home/admin/logs/tracking-center/gc.log 
-Xmx8192m 
-Xms8192m 
...

使用 CMS 收集器,设置了堆内存为 8G

查看 JVM 运行时参数

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 8589934592 (8192.0MB)
   NewSize                  = 348913664 (332.75MB)
   MaxNewSize               = 348913664 (332.75MB)
   OldSize                  = 8241020928 (7859.25MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

young 大小 332.75MB

那么问题来了: Q:JVM 的 newRatio 参数默认为 2,按这个配置,新生代应该为 8G*1/3, 差别太大了 A:JVM 有动态调整新生代和老年代大小的机制,1.8 中默认是自动开启的 Q:这么智能,怎么会在新生代最需要内存的时候只给分了 332MB 去另一台服务上确认一下配置,发现相同的启动参数,新生代大小也是 332MB Q:怎么都是 332MB,动态调整新生代和老年代的机制没生效吧 Q:332 这个数字很有内涵,google young 332 根据下面两篇博文找到了缘由:

默认情况下和 cpu 核数有关,ScaleForWordSize 的值大约是 64M * 4 * 13 / 10 = 332.8M,再做下对齐就得到 332.75M 了;( 见参考资料:CMS GC 默认新生代是多大?)

Q:回到之前的问题,为什么 newRatio 参数默认为 2 没有生效 A:想起来 jdk 1.8 新生代默认的收集器并不是 CMS,是 ParallelGC。 于是继续 google 1.8 cms newRatio,找到了一篇 JVM bug 报告,在 1.8 中使用 CMS 收集器会导致默认的 newRatio 不生效,解决办法:在启动参数中显式配置一次,或者将新生代大小设置为固定值.https://bugs.openjdk.java.net/browse/JDK-8153578

Q:为什么动态调整没有生效 在 JDK1.7 中如果开启了 -XX:+UseAdaptiveSizePolicy 配置项,JVM 将会动态调整 Java 堆中各个区域的大小以及进入老年代的年龄,–XX:NewRatio 和 -XX:SurvivorRatio 将会失效. 而 JDK1.8 是默认开启 -XX:+UseAdaptiveSizePolicy 配置项的. 但使用 CMS 收集垃圾时会关闭 UseAdaptiveSizePolicy.

最后回顾一下整个问题:

1.jdk 1.8 中使用 CMS 收集器,UseAdaptiveSizePolicy 参数会被设置为 false,导致 young 区和 old 区大小不会动态调整

2.jdk 1.8 中使用 CMS 收集器,默认的 newRatio=2 不会生效,需要显示配置此参数或者配置 young 大小。否则按照 cpu 核心数量计算 young 大小:64M * cpu 核心数 * 13 / 10

3.批量任务每次任务量过大,短时间内创建大量对象且不释放,导致 jvm 疯狂的 young gc

4.频繁 young gc(100 次 / 秒)导致 CPU 使用率过高,系统吞吐量下降

三、解决方案

1.显式调整新生代大小 将 newRatio 调整为 3 2.离线任务错峰执行 批量任务调整为非业务高峰期执行

3.代码优化

  • 减少定时任务每次执行的任务量
  • 降低定时任务执行频率
  • 大方法拆解:方法如果过长,在执行的过程中早期创建的对象没有释放,无法回收;抽象拆解成小方法,执行完便释放临时对象引用

在发布之后,CPU 使用率和 GC 次数回到合理的范围内 按小时统计在 24 分完成发布并开启定时任务:

随后系统的运行情况:

总结

  • 在启动服务时即便配置了 JVM 参数,在启动后也要检查一下是否生效,因为 JVM 中有一些隐式规则
  • 发现问题时使用工具快速定位问题,这次使用 arthas 查看资源占用的实际情况
  • 避免在代码中创建大对象,或者批量创建大量对象
  • 避免方法过长,导致临时对象无法及时回收
  • 在业务高峰期关注服务监控指标
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 一、现象
  • 二、排查过程
  • 三、解决方案
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档