性能调优(CPU飙高 FullGC问题处理)

1. 前言 最近产品在普通压测下并没有实际问题,但是在实际环境的大数据(日数据量在1.3亿左右)情况下便出现了一些列的性能问题,因此进行一系列的情况调优
当然每种问题处理的方案不一样,这里只是记录个人解决问题的方案与步骤
2. cpu标高 cpu标高问题是由一系列问题导致的,具体可以通过以下两种方案去进行处理:

  • 通过jdk提供的工具去进行处理
  • 通过arthas工具去进行处理
当然这种情况的前提时java进程的日志无报错,只是CPU使用率较高的情况,如果有明显的报错
那么就可以通过错误日志去进行诊断
2.1 arthas
arthas是由阿里巴巴开源的一个性能诊断工具,这里就不再赘述,直接上演示步骤,具体如下:
a. 定位进程
当CPU标高时,可以通过top指令去查看,如下图:
性能调优(CPU飙高 FullGC问题处理)
文章图片

从图中可以看出CPU的总使用率在2.8%左右,但是从具体的进程来看有的java进程在31%左右
当然这肯定是正常的,这里只是举了个例子,每个情况不一样,需要具体分析
当然安装了`htop命令,通过htop命令去查看CPU每个核具体的使用情况,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

b. 通过top指令定位以后,则可以通过具体的jps命令去查看占用较多的是哪个进程,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

可以发现是log-service目前占用较高,那么就可以查看这个进程的问题
c.启动arthas工具,具体怎么使用这里就不再赘述,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

这里选择3-log-service进程,输入dashboard命令便可以看到这个进程的监控信息如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

输入thread -n 8命令可以查看到cpu使用率最高的八个线程日志,cpu利用率从高到低排列,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

最后找到自己程序的代码行数,分析这行代码占用性能的原因
2.2 影响
从目前优化的过程来看,主要有两大因素(针对的是自己的系统)比较消耗资源,如下:
  • 正则表达式匹配
  • 数据库操作
2.2.1 正则 A.问题
在JAVA中,正则表达式匹配采用的是NFA(Non deterministic Finite Automaton)去进行匹配,该机制在遇到像 +,*等不确定字符串时,会进行 回溯
在一些数据量大,且写的正则表达式不是那么合格时,就会进行不断回溯,从而会特别消耗CPU资源
在这里关于具体详情可以推荐一遍他人的博客
B 解决
  1. 尽量少用不合格的正则去匹配,如果要使用正则,且数据量大的情况下,进行尽量减少回溯
  2. 尽量少用一些底层是正则表达式实现的字符串拼接方式,例如String.format
一切的前提都是基于数据量大的情况,数据量小请忽略,
可以用Jmeter 等工具去进行性能压测,验证上述结果
2.2.2 数据库 这种理论较为简单,即在一个循环里面频繁操作数据库,如下:
for(String ss : aa) { database_opertor() }

这种方式每循环一次,那么就会操作发送一次数据库连接,在数据量较大的情况下就会特别消耗CPU资源
因此建议将多个数据库连接改为一个或者降低数据库连接方式,如下:
  • 查询一次,具体结果可以通过Stream流处理
  • 批量新增
  • 批量更新
  • 批量删除
一切的前提都是基于数据量大的情况,数据量小请忽略,
可以用Jmeter 等工具去进行性能压测,验证上述结果
3. GC 关于GC问题,主要是针对JVM一直Full GC问题,在这里也记录一下FullGC过程,具体过程如下:
A.问题:
测试人员通过Grafana监控到Java组件内存一直成上升趋势,那么可能就会存在内存泄露风险,如下图:
性能调优(CPU飙高 FullGC问题处理)
文章图片

后来经过一系列分析,可能是JVM一直在FullGC导致,因此为了验证这个想法,在这里借助了jvisualvm工具去进行分析(在附文中会详细阐述使用该工具),分析结果如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

发现老年代直接拉满,当老年代拉满后,就会频繁FullGC,但是经过几轮GC后,发现还是拉满,这个时候就需要分析为什么会存在拉满原因
当我把对应的组件重启后,堆里面old很快爆满,然后频繁FullGC
因此可以用过jmap命令把堆内存中的信息dump下来,如下:
jmap -dump:file=filename.dump pid

命令执行后会产生一个filename.dump文件,通过jvisualvm工具分析这个文件,就知道对堆里面对象信息是什么了
B.原因
通过jmap产生的dump文件分析里面都是消费kafka的消息,也就是说old里面都是消费kafka的消息
之所以old爆满,是因为消费kafka消息太多,当第一次启动时,Eden区爆满,经过第一轮YGC,大部分对象还没处理完,因此直接进入了s0
但是由于进入了s0区的对象超过了s0区容量的一半,因此就不再计算年龄,而是直接从s0进入到老年代,所以经过几次消费old一直处于爆满状态,然后一直FullGC,导致内存一直增长,最后存在内存泄露
C.解决
其实上述问题可以看出,其实还是老年代空间较小,我的组件配置为-Xms6G -Xmx6G -Xmn5G,也就是说我的配置老年代也就只有1G的空间,因此解决方案如下:
  • 调小消费kafka的数量,这样数据量降低了也就不存在频繁FullGC情况
    这种方案最后放弃了,因为调小了消费的数据量,性能就达不到要求
  • 调大堆内存,增加old空间大小
    这种也放弃了,资源有限,不太好做调整了
  • 切换G1垃圾回收器
    最终采用的该方案,G1(JAVA 9默认的垃圾回收器就是这,目前我使用的时JAVA 8),该垃圾回收器与默认的垃圾回收器不同,这个并没有物理上的分代,而是将一整块内存,划分成一个个的小内存,并且给内存打上old,eden,s0,s1标记(具体可以自己了解),切换后整体如下:
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    完美解决了我的困扰
4.附文
这一章主要是阐述Jvisualvm工具的使用
4.1启动
JAVA_HOME/bin/目录下
性能调优(CPU飙高 FullGC问题处理)
文章图片

双击即启动,启动界面如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

4.2.介绍
性能调优(CPU飙高 FullGC问题处理)
文章图片

在本地中选择VisualVM在右边即可看到对应的JVM信息,同时可以在监视中看到对应的区域信息,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

也可以在线程中看到所有的线程情况,如下:
性能调优(CPU飙高 FullGC问题处理)
文章图片

当然也可以安装一个Visual GC插件查询实时GC情况,安装步骤如4.3所述
4.3 插件
介绍如何安装插件
1.访问插件更新地址:https://visualvm.github.io/in...
性能调优(CPU飙高 FullGC问题处理)
文章图片

  1. 选择plguins
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

  2. 找到Plugins Centers
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

  3. 点击后选择对应的jdk版本插件,我的jdk版本为java8_u40版本
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

5.打开jvisualvm,找到 工具-插件,选择编辑,填上对应的jdk插件地址
性能调优(CPU飙高 FullGC问题处理)
文章图片

  1. 找到对应插件安装即可
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

  2. 安装好后可以点击Visual GC查看实时GC情况
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

4.4 远程
正常情况下,都是将jar部署在远程机器上,因此想要查看实时GC就可以通过远程连接进行查询
一般远程连接有两种方式:
  • JMX连接
    这种方式连接一般只能监控一个java服务
  • Jstatd连接
    这种方式连接可以监控多个java服务
4.4.1 JMX
  1. 在远程Java服务上配置JMX连接,如下:
    -Djava.rmi.server.hostname=服务器ip -Dcom.sun.management.jmxremote.port=jmx端口 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

  2. Jvisualvm上新建一个远程连接
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    填写对应的主机名

    填写好了如下:
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

  1. 在连接名上右键选择添加一个JMX连接
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    端口号要与服务端配置的一样,接下来点击连接即可,当然界面与之前本地是一样的,如下:
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    但是当点开VisualGC时,却显示如下结果:不受此JVM支持,因此可以使用Jstatd进行连接
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

4.4.2 Jstatd
  1. java服务新建jstatd-all.policy,文件可以放在任意位置,内容如下
    // 注意:这里建议写JAVA_HOME的绝对路径 grant codebase "file:${JAVA_HOME}/lib/tools.jar" { permission java.security.AllPermission; };

    最后执行jstatd -J-Djava.security.policy=/opt/jstatd.all.policy -p 12345命令,如果出现以下问题,安装下面配置即可
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    最后发现权限不足,因此去进行授权,修改${JAVA_HOME}/jre/lib/security/java.policy文件
    permission java.io.FilePermission "/tmp/-", "read"; permission java.util.PropertyPermission "*", "read"; permission java.net.SocketPermission "*", "connect,resolve,accept,listen"; permission java.util.PropertyPermission "*", "read";

    在文件中加入上述内容,如下:
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

  2. 再次执行命令,即可成功
    jstatd -J-Djava.security.policy=/opt/jstatd.all.policy -p 端口 -J-Djava.rmi.server.logCalls=true -J-Djava.rmi.server.hostname=远程服务器ip

  3. 连接jstatd,右键新建jstatd连接
    性能调优(CPU飙高 FullGC问题处理)
    文章图片

    1. 最后可以看到所有的组件信息
      性能调优(CPU飙高 FullGC问题处理)
      文章图片

      最后随便点开一个组件便可看到对应的GC情况
      【性能调优(CPU飙高 FullGC问题处理)】性能调优(CPU飙高 FullGC问题处理)
      文章图片

    推荐阅读