GC导致线上CPU超100%

运维同学告知报警,一个java进程占用CPU 100%以上。下面进行排查步骤。
1.top -c ,效果如下图(这个截图是另一个节点的效果,实际进程号为17817,后续的截图将都会以进程号17817为准)。
GC导致线上CPU超100%_第1张图片
2.top -pH 17817,获取17817进程中,CPU消耗大的两个线程。
GC导致线上CPU超100%_第2张图片
3.printf “%x\n” 17819 ,获取16进制的线程号,因为在jstack的报告中,线程号都是以16进制表示的。
GC导致线上CPU超100%_第3张图片
4.jstack 17817 > 17817.jstack,将jstack的报告输出到17817.jstack文件中。
4.1.然后less 17817.jstack,搜索上面的16进制的线程号,结果如下。发现,是两个gc线程在疯狂占用CPU,由此可知,jvm内存不够,或者,java代码写得有漏洞导致对象无法回收。
GC导致线上CPU超100%_第4张图片
5.jmap -heap 17817 > 17817.heap ,查看jvm堆快照,发现jvm堆内存一共500M,新生代和老年代各250M,且新生代的eden区一直处于100%利用状态,老年代也一直处于99.87%被利用的状态。之所以From和To两个区域的used为0%,是因为它们的空间(15M)根本无法容纳eden区回收之后剩余的对象,所以直接都去了老年代(个人猜测而已)。
GC导致线上CPU超100%_第5张图片
6.jmap -dump:format=b,file=17817.dump 17817,导出内存dump,输出到17817.dump文件中。628M大小。
GC导致线上CPU超100%_第6张图片
7.利用jdk自带工具jvisualvm分析内存dump文件。在mac下,可以直接在终端窗口输入命令jvisualvm,即可打开jvisualvm图形界面窗口,在windows下可以直接双击jvisualvm.exe。
打开jvisualvm图形界面窗口之后,点击文件->装入,在文件类型那一栏选择堆,选择要分析的17817.dump文件,打开。
GC导致线上CPU超100%_第7张图片
效果如下:

发现char[]占用内存达到了276733352,应该是263M(新生代一共才250M,总堆内存一共才500M)。然后查看其实例情况,如下图,有大量的字符串对象(包括各种json、大量的SELECT/UPDATE的sql语句,还有一些类似token字符串的东西,等等):




综上,cpu飙高的原因已经找到,即为gc导致,而gc的原因表象也已经找到,是存在大量字符串对象。但是,为什么会存在这么多字符串对象,需要业务开发人员去看自己的代码,这些字符串是怎么产生的,为什么会产生这么多,却gc不掉。

个人建议:
这个项目的启动参数是-Xms500m -Xmx500m -Xmn250m,而实际运行时的堆内存参数,如上面的截图:
GC导致线上CPU超100%_第8张图片
由此可知,在启动参数中设置了新生代大小为250m(-Xmn250m),就会覆盖默认的-XX:NewRatio=2(即新生代:老年代=1:2),导致现在的新生代和老年代都是250m,即1:1。
所以,应该去掉-Xmn250m这个参数,而根据比例来设置其与老年代的占比。

其他参考如下:
来自:https://blog.csdn.net/yrwan95/article/details/82826519

Xmn、Xms、Xmx、Xss都是JVM对内存的配置参数,我们可以根据不同需要区修改这些参数,以达到运行程序的最好效果。
-Xms 堆内存的最小大小,默认为物理内存的1/64
-Xmx 堆内存的最大大小,默认为物理内存的1/4
-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn
-Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
除了这些配置,JVM还有非常多的配置,常用的如下:
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRatio:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。

你可能感兴趣的:(jvm,线上问题)