乐趣区

关于后端:性能调优CPU飙高-FullGC问题处理

1. 前言

最近产品在一般压测下并没有理论问题,然而在理论环境的大数据 (日数据量在 1.3 亿左右) 状况下便呈现了一些列的性能问题,因而进行一系列的状况调优

当然每种问题解决的计划不一样,这里只是记录集体解决问题的计划与步骤

2. cpu 标高

cpu标高问题是由一系列问题导致的,具体能够通过以下两种计划去进行解决:

  • 通过 jdk 提供的工具去进行解决
  • 通过 arthas 工具去进行解决

当然这种状况的前提时 java 过程的日志无报错,只是 CPU 使用率较高的状况,如果有显著的报错

那么就能够通过谬误日志去进行诊断

2.1 arthas

arthas是由阿里巴巴开源的一个性能诊断工具,这里就不再赘述,间接上演示步骤, 具体如下:

a. 定位过程

当 CPU 标高时,能够通过 top 指令去查看,如下图:

从图中能够看出 CPU 的总使用率在 2.8% 左右,然而从具体的过程来看有的 java 过程在 31% 左右

当然这必定是失常的,这里只是举了个例子, 每个状况不一样,须要具体分析

当然装置了 `htop 命令,通过 htop 命令去查看 CPU 每个核具体的应用状况,如下:

b. 通过 top 指令定位当前,则能够通过具体的 jps 命令去查看占用较多的是哪个过程,如下:

能够发现是 log-service 目前占用较高,那么就能够查看这个过程的问题

c. 启动 arthas 工具,具体怎么应用这里就不再赘述,如下:

这里抉择 3-log-service 过程,输出 dashboard 命令便能够看到这个过程的监控信息如下:

输出 thread -n 8 命令能够查看到 cpu 使用率最高的八个线程日志,cpu利用率从高到低排列,如下:

最初找到本人程序的代码行数,剖析这行代码占用性能的起因

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 组件内存始终成回升趋势,那么可能就会存在内存泄露危险,如下图:

起初通过一系列剖析,可能是 JVM 始终在 FullGC 导致,因而为了验证这个想法,在这里借助了 jvisualvm 工具去进行剖析(在附文中会具体论述应用该工具),剖析后果如下:

发现老年代间接拉满,当老年代拉满后,就会频繁 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 标记(具体能够本人理解), 切换后整体如下:

    完满解决了我的困扰

4. 附文

这一章次要是论述 Jvisualvm 工具的应用

4.1 启动

JAVA_HOME/bin/ 目录下

双击即启动,启动界面如下:

4.2. 介绍

在本地中抉择 VisualVM 在左边即可看到对应的 JVM 信息,同时能够在 监督 中看到对应的区域信息,如下:

也能够在线程中看到所有的线程状况,如下:

当然也能够装置一个 Visual GC 插件查问实时 GC 状况,装置步骤如 4.3 所述

4.3 插件

介绍如何装置插件

1. 拜访插件更新地址:https://visualvm.github.io/in…

  1. 抉择plguins

  2. 找到Plugins Centers

  3. 点击后抉择对应的 jdk 版本插件,我的 jdk 版本为 java8_u40 版本

5. 关上 jvisualvm,找到 工具 - 插件 ,抉择编辑,填上对应的jdk 插件地址

  1. 找到对应插件装置即可

  2. 装置好后能够点击 Visual GC 查看实时 GC 状况

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 上新建一个近程连贯

    填写对应的主机名

    填写好了如下:

  1. 在连贯名上右键抉择增加一个 JMX 连贯

    端口号要与服务端配置的一样,接下来点击连贯即可,当然界面与之前本地是一样的,如下:

    然而当点开 VisualGC 时,却显示如下后果:不受此 JVM 反对 ,因而能够应用Jstatd 进行连贯

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 命令,如果呈现以下问题,装置上面配置即可

    最初发现权限有余,因而去进行受权,批改 ${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";

    在文件中退出上述内容,如下:

  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 连贯

    1. 最初能够看到所有的组件信息

      最初轻易点开一个组件便可看到对应的 GC 状况

退出移动版