第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 性能测试瓶颈分析与系统调优(9)java程序GC机制及性能稳定性调优分析

性能测试瓶颈分析与系统调优(9)java程序GC机制及性能稳定性调优分析

时间:2023-09-07 15:33:10

相关推荐

性能测试瓶颈分析与系统调优(9)java程序GC机制及性能稳定性调优分析

8.1 jvm资源监控工具

8.1.1jconsole监控工具

jmap:此工具在jdk安装目录的bin文件夹里面

jmap [option]<pid>

例如:jmap -heap 6033

MaxHeapSize =573870912(512.0MB)

jconsole【被取代了】:此工具在jdk安装目录的bin文件夹里面,运行后界面

可以查看内存、线程、类、jvm等相关信息

本地进程访问,选择对应的java程序进程即可

远程访问需要服务器开放对应的端口,启动时,添加一下对应的配置

-Dcom.sum.management.jmxremote

-Djava.rmi.server.hostname=192.168.1.15 这个ip写用于访问的ip,要确定你的jconsole客户端能够通过这个ip访问到服务器

-Dcom.sun.management.jmxremote.port=8999 给jconsole连接的端口

-Dcom.sun.management.jmxremote.rmi.port=9999

-Dcom.sun.management.jmxremote.ssl=false 取消ssl加密

-Dcom.sun.management.jmxremote.autheticate=false 取消用户名密码验证

服务器启动项目时,将这些参数加进去,启动命令为:

java -Xmx2G -Dcom.sun.management.jmxremote

-Dcom.sun.management.jmxremote.port=8999

-Dcom.sun.management.jmxremote.rmi.port=9999

-Djava.rmi.server.hostname=192.168.1.5

-Dcom.sun.management.jmxremote.ssl=false

-Dcom.sun.management.jmxremote.authenticate=false

-jar -Dspring.datasource.url="jdbc:mysql://192.168.1.7:3306/novel-plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" -Dspring.datasource.username=root -Dspring.datasource.password=zhaoWEILI1314520@ novel-front-3.5.4.jar

在客户端jdk安装目录下的 bin目录,打开jconsole

注意:连不上,可以关闭防火墙,前面介绍别的工具安装,配置防火墙,让防火墙放行单个端口的设置去操作,需要将8998端口和9999端口一起给它配置了

firewall-cmd --add-port=8999/tcp --permannet

firewall-cmd --add-port=9999/tcp --permannet

Firewall-cmd --reload #重载防火墙

建议直接关闭防火墙 systemctl stop firewalld

对服务器进行压测,检测在测试过程数据的变成情况

8.1.2 jvisualvm监控工具(取代了jsconsole)

jvisualvm功能比jconsole强大

官网下载地址:https://visualvm.github.io/releases.html

解压后,在bin目录中 找到.exe文件,打开它,创建远程连接

这个工具和jconsole相关,服务器启动项目时,将jscnsole这些参数加进去;

添加jmx,增加端口号8999

接下来就可以查看远程主机的一些信息

被监控服务器上启动jstatd,jvm jstatd Daemon守护进程,一个RMI(Remote Method Invocation)服务器程序,用于监控本地所有jvm从创建开始,知道销毁整个过程中的资源使用情况,同时 提供接口给监控工具 (如这里的VisualVM)让工具能连接到本地所有的jvm

该命令在jdk的bin目录下:

创建安全策略文件:在jdk的bin目录下新建 jstatd.all.policy的文件

内容如下:

grant codebase "file:${java.home}/../lib/tools.jar" {

permission java.security.AllPermission; };

注意:服务器记得配置JAVA_HOME环境变量

启动:

jstatd -J-Djava.security.policy=/usr/local/jdk/bin/jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.5

添加jstatd

注意:jvisualvm此功能要求服务器启动程序时和jconsole一样的请求参数

特别注明:jdk1.8,在bin目录,自带中文版的该工具,在jdk的bin目录下

在测试过程中可以通过抽样器对测试进行过程进行分析,找到最耗费cpu和内存的方法,从而协助开发有针对性的对代码进行优化,而不是优化所有代码。

8.1.3 jvm集群监控体系

基于prometheus+grafana

官网下载地址:/prometheus/jmx_exporter

将jar包传到java应用服务器上,在jar同级目录下创建config.yaml

内容为(通用配置):

---

lowercaseOutputLabelNames: true

lowercaseOutputName: true

whitelistObjectNames: ["java.lang:type=OperatingSystem", "Tomcat:*"]

blacklistObjectNames: []

rules:

- pattern: 'java.lang<type=OperatingSystem><>(committed_virtual_memory|free_physical_memory|free_swap_space|total_physical_memory|total_swap_space)_size:'

name: os_$1_bytes

type: GAUGE

attrNameSnakeCase: true

- pattern: 'java.lang<type=OperatingSystem><>((?!process_cpu_time)\w+):'

name: os_$1

type: GAUGE

attrNameSnakeCase: true

- pattern: 'Tomcat<type=Server><>: (.+)'

name: tomcat_serverinfo

value: 1

labels:

serverInfo: "SpringBoot Tomcat"

type: COUNTER

- pattern: 'Tomcat<type=GlobalRequestProcessor, name=\"(\w+-\w+)-(\d+)\"><>(\w+):'

name: tomcat_$3_total

labels:

port: "$2"

protocol: "$1"

help: Tomcat global $3

type: COUNTER

- pattern: 'Tomcat<j2eeType=Servlet, WebModule=//([-a-zA-Z0-9+&@#/%?=~_|!:.,;]*[-a-zA-Z0-9+&@#/%=~_|]), name=([-a-zA-Z0-9+/$%~_-|!.]*), J2EEApplication=none, J2EEServer=none><>(requestCount|processingTime|errorCount):'

name: tomcat_servlet_$3_total

labels:

module: "$1"

servlet: "$2"

help: Tomcat servlet $3 total

type: COUNTER

- pattern: 'Tomcat<type=ThreadPool, name="(\w+-\w+)-(\d+)"><>(currentThreadCount|currentThreadsBusy|keepAliveCount|connectionCount|acceptCount|acceptorThreadCount|pollerThreadCount|maxThreads|minSpareThreads):'

name: tomcat_threadpool_$3

labels:

port: "$2"

protocol: "$1"

help: Tomcat threadpool $3

type: GAUGE

- pattern: 'Tomcat<type=Manager, host=([-a-zA-Z0-9+&@#/%?=~_|!:.,;]*[-a-zA-Z0-9+&@#/%=~_|]), context=([-a-zA-Z0-9+/$%~_-|!.]*)><>(processingTime|sessionCounter|rejectedSessions|expiredSessions):'

name: tomcat_session_$3_total

labels:

context: "$2"

host: "$1"

help: Tomcat session $3 total

type: COUNTER

程序启动时添加对应的agent和配置:

java -javaagent:./jmx_prometheus_javaagent-0.18.0.jar=12345:config.yaml -jar

-Dspring.datasource.url="jdbc:mysql://192.168.1.7:3306/novel-plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" -Dspring.datasource.username=root -Dspring.datasource.password=zhaoWEILI1314520@ novel-front-3.5.4.jar

java程序启动后,会开放一个12345的端口.通过http去访问这个端口,可以获取jvm的运行状态数据,配置防火墙策略:

firewall-cmd --add-port=12345/tcp --permanent

firewall-cmd reload

接下来:Prometheus配置数据采集,修改yml配置文件,添加监控,配置后重新启动

单独创建了一个任务--jvms,这个工具就会自动的调节前面启动监控数据接口,从而获取jvm运行时的数据

Grafanap配置监控大屏:

在garfana官网下载监控jvm,然后导入json文件,前面有介绍 ,就不写了,

注意点,jvm在导入时,需要主要job名称,要跟prometheus.yml文件中配置的一致,不然会没有数据

对服务器进行压测,使用jvm监控看板,查看jvm资源使用情况

实战演示:启动java程序时,设置堆内存大小为200M,对服务器进行压测

记录测试时间点:-03-21 13:56:23 --03-21 13:58:09

其中吞吐量最大为:3.39k,平均3.7k,最小223.20

在这个时间段内,java应用服务器的cpu使用率最大为56.81%,内存20.45%

数据库服务器cpu的使用率最大为1.61%,内存40.04%

总结:cpu,内存,磁盘,网络资源使用率不高,程序出现瓶颈,最大吞吐量3.39k,应为程序的问题,查看jvm信息监控看板,jvm,cpu最高为45%,堆内存61.4%,jvm不存在明显的

某个资源使用率过高问题,相对并发数量设置过小,加大并发数,继续排查问题。

这里将堆内存设置为50M,对比看一下,设置前

将堆内存调增为50M后,使用同样的脚本对服务器进行压测,堆内存基本上满了

8.2GC垃圾回收

8.2.1自动垃圾收集机制

内存不是无穷无尽的,每一个请求处理过程中,都会创建一些对象,占用一些内存。请求

处理完毕,占用内存应该被释放。JVM内置垃圾回收机制,JVM程序里面有一个模块的垃圾回收器。

java垃圾回收过程:标记垃圾,清除垃圾,整理内存

8.2.2分代回收机制

新生代-eden --Xmn

新生代-survivor 1

新生带-survivor 2

老年代-XX:pretenureSizeTHreshold=1024 升级到老年代的对象定义

两大区域--

小范围GC,大范围Full GC 两种

新生代-新对象新数据

老年代-超级大的对象,经历多次新生代GC,依然存在的对象

特殊--永久代【元数据空间】

8.2.3 STW概念(stop the world)

和性能有关:一个请求--处理实际

业务代码执行时间+ GC垃圾回收时间

JVM在GC的时候,业务代码线程,处于停止等待的一个状态,就好比打扫垃圾的时候,总不能边扔边打扫,如果是这样的情况,那么业务代码在执行的过程,就会出现,代码还没执行完毕,数据被当作垃圾清理掉了,找到数据,程序运行就会出错。

8.2.3 minor gc小范围GC

新生代--时间一般比较短

8.2.4 full gc大范围GC

所有的堆空间、元数据空间

导致程序中断的时间比较长

8.2.5垃圾回收器

串行GC:基本不用

并行GC:PS

并发GC:cms G1

实战1:StackOverflowError -栈内存溢出

线程栈内存默认大小1M:每个线程都会分配一个小内存区块,保存线程执行的方法、局部变量的信息。

建议代码优化,:出现场景--递归调用--递归--方法嵌套调用自己

栈--数据结构--后进先出--每一次执行方法之前,把方法信息,压入栈,从上往下执行

递归:方法调用没完毕,又调用自身……,如果长时间不结束,导致栈内存不够

确实有大量递归场景,尝试修改配置,增加JVM每个线程栈内存的大小

-Xss参数:java每个线程的stack大小

jdk5.0以后,每个线程堆栈大小为1M,以前为256K

在相同的物理内存下,减少这个值能生成更多的线程

物内存是固定,增大栈内存后,可运行的线程数变少

实战2:OutOfMemoryError-内存溢出

持久性压力测试,程序内存占用逐步增高,最终挂掉,突然大量并发,内存不够i用

java.lang.OutOfMemoryError:unable to create new native thread:java程序已经达到了它可以启动的线程数的限制

java.lang.OutOfMemoryError:Metaspace

1.8+java.lang.OutOfMemoryError:permGen space

-XX:MaxPermSize=512m

加大元数据空间的内存,或检查元数据空间占是否合理,class类加载的太多,动态创建了很多class类

java.lang.OutOfMemoryError:java heap space

Java.lang.OutOfMemoryError:GC overhead limit exceeded

加大堆内存,或者检查内存占用是否合理

程序调优-获取内存快照-占到占用比较大的对象,让开发修改

JVM在遇见OOM(OutOfMemoryError)时生成内存快照文件:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp

增加这些JVM启动参数

在启动项目时,调整项目的栈内存为2M,堆内存为200M,对服务器进行压测,内存一旦溢出就会保存文件到reading/test目录下

java -Xss2m -Xmx200m

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/reading/test

-javaagent:./jmx_prometheus_javaagent-0.18.0.jar=12345:config.yaml -jar

-Dspring.datasource.url="jdbc:mysql://192.168.1.7:3306/novel-plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" -Dspring.datasource.username=root -Dspring.datasource.password=zhaoWEILI1314520@ novel-front-3.5.4.jar

此时执行压测脚本,在对应目录下,创建对应的java文件

内存快照分析:

工具MAT -eclipse Memory Analyzer Tools

下载地址:/mat/downloads.php

下载一个高版本的jdk,我这里下载了1个19的

修改配置文件:MemoryAnalyzer.ini,增加一行

-vm

E:\jdk-19\bin\javaw.exe通过mat打开内存快照 #这个为更改版本jdk的javaw,exe的安装位置

使用该工具,打开从服务器上下载的java_pid2818.hprof文件

首先打开Histogram(直方图),查看每个对象占用了多少内存,但具体是那段代码占用不知道,直方图中,右键,list object菜单中,查看引用对象

with incoming references 表示的是当前查看的对象,被外部应用

with outGoing references 表示的是当前对象,引用了外部对象

接下来看dominator tree(支配树)

当在支配树视图中选中某一对象时,我们还可以通过Path To GC Roots 功能,反向列出该对象到 GC Roots 的引用路径

到了这个程度,开发还找不到,在Reports分析中,Leak Suspects,去找可疑点,点击details

8.3性能稳定性调优

实战:性能测试过程中,吞吐量呈现出波浪状,性能稳定性调优

疑因分析:JVM垃圾回收,导致STW产生时间停顿

波浪小:可能是小规模GC比较多,或者每次GC时间补偿,能接收就行

波浪起伏大,GC时间长,可能是产生了多次Full gc,调整垃圾回收器参数,减少停顿时间

最优先的是让开发从编码的角度去优化代码,减少内存占用、复用内存,90%的情况下,不需要做大量所谓垃圾回收的调整

我们对服务器进行压测

8.3.1垃圾回收器优化思路

并发量不变,垃圾不会减少

垃圾不减少,那就提高垃圾回收的次数,每次少清理一些垃圾,时间则会减少

8.3.2GC是否需要优化

响应时间角度:要求:所有用户请求必须在1000ms内完成,对于响应时间的要求,根据经验来说,我们要求GC占用的时间不超过10%,为了方便理解,假定一次请求最多经过一次GC。也就是一个请求要求1000ms完成,则GC导致STW的时间不能超过100ms,只要在这个范围内,就是符合要求的(也就是无需优化)

耗时=业务代码执行时间+GC-STW时间

吞吐量角度:要求:一定时间内处理完多少此请求,例如:1分钟处理完毕6000此请求,则平均每秒处理100次,追求吞吐量的情况下,我们就不去细说一次GC的时间,我们要统计的就是这段时间内GC消耗的总时间,同样,根据经验值,GC占用的总时间不应超过10%

8.3.4GC相关参数

自适应参数:

单次最大暂停时间(STW时间): -XX:MaxGCPauseMillis=200 JVM尽量保证把每次GC的时间控制在这个时间内

应用程序吞吐量目标:-XX:GCTimeRatio=19 如果参数设置为19,则垃圾回收总时间小于应用程序总运行时间的5%,计算公式:GC总时长限值==1/(1+GCtimeRation)*程序总时长

这两个参数配置后,JVM会根据运行情况,自动动态调整堆中分代各区域的大小。在这种动态调整的情况下,JVM自行选择。

同时配置,优选保证的顺序:最大暂停时间目标>吞吐量目标

分代空间大小配置:-Xmn 新生代代大小

-XX:newSize 新生代初始化内存的大(注意:该值需要小于-Xms的值)

-XX:MaxnewSize 新生代可被分配的内存的最大上限(注意:该值需要小于-Xmx的值)

一个参数顶两个细分参数

-XX:NewRatio=2 默认新生代和老年代比例1:2

-XX:SurvivorRatio=8 默认情况下Eden:from:to=8:1:1

大对象的判定标准,通过-XX:+pretenureSizeThreshold控制

切换不同的垃圾回收器,这个是用的最多的GC方面优化手段

-XX:+UseSerialGC :服务器基本不用,单线程去做垃圾回收

--XX:+UseParallelGC -XX:+UseParallelOldGC:大部分默认这种,并行多线程回收

--XX:+UseConcMarkSweepGC:响应时间要求高的选择这一种,并发多线程回收

GC的多个阶段中,有一些阶段业务线程代码依旧可以运行,不像并行多线程,GC 全都是暂停业务线程

G1 GC,全程Garbage-Firest Garbage Collector,通过-XX:+UseG1GC:大内存服务器选择这一种,并发多线程回收

预申请内存:-XX:+AlwaysPreTouch

每个java应用启动参数必加

每个java应用启动之后梦想操作系统把内存全部申请到位

JVM配置了2G内存,不代表马上就有2G内存归属JVM,默认用了多少,操作系统给它多少,当值JVM要用的时候,操作系统没用足够内存,启动之后,提前申请

GC调优--所有jvm核心开发团队,不断努力提升的方向,参数很多,调了某一个参数不一定管用。多次配置修改,反复尝试,大规模落地--几家大厂核心业务系统

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。