JVM系列-9.性能调优

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • 性能调优
    • 性能调优解决的问题
    • 性能调优的方法
      • 案例1:CPU占用率高问题的解决方案
      • 案例2:接口响应时间很长的问题
        • Arthas的trace命令
        • Arthas的watch命令
      • 案例3:定位偏底层的性能问题
        • Arthas的profile命令
      • 案例4:线程被耗尽问题
      • 更精细化的性能测试
        • 案例:日期格式化方法性能测试
        • 性能调优综合实战

性能调优

性能优化的步骤总共分为四个步骤,其中修复部分要具体问题具体分析且处理方式各不相同。

在这里插入图片描述

性能调优解决的问题

应用程序在运行过程中经常会出现性能问题,比较常见的性能问题现象是:

1、通过top命令查看CPU占用率高,接近100甚至多核CPU下超过100都是有可能的。

在这里插入图片描述

2、请求单个服务处理时间特别长,多服务使用skywalking等监控系统来判断是哪一个环节性能低下。

在这里插入图片描述

3、程序启动之后运行正常,但是在运行一段时间之后无法处理任何的请求(内存和GC正常)。

性能调优的方法

线程转储(Thread Dump)提供了对所有运行中的线程当前状态的快照。线程转储可以通过jstack、visualvm等工具获取。其中包含了线程名、优先级、线程ID、线程状态、线程栈信息等等内容,可以用来解决CPU占用率高、死锁等问题。

在这里插入图片描述

先使用jps 查看对应的进程号,然后使用jstack 进程号即可。或者使用visualvm的 thread dump。

线程转储(Thread Dump)中的几个核心内容:

◆ 名称: 线程名称,通过给线程设置合适的名称更容易“见名知意”

◆ 优先级(prio):线程的优先级

◆ Java ID(tid):JVM中线程的唯一ID

◆ 本地 ID (nid):操作系统分配给线程的唯一ID

◆ 状态:线程的状态,分为:

NEW – 新创建的线程,尚未开始执行

RUNNABLE –正在运行或准备执行

BLOCKED – 等待获取监视器锁以进入或重新进入同步块/方法

WAITING – 等待其他线程执行特定操作,没有时间限制

TIMED_WAITING – 等待其他线程在指定时间内执行特定操作

TERMINATED – 已完成执行

◆ 栈追踪: 显示整个方法的栈帧信息

案例1:CPU占用率高问题的解决方案

问题:

监控人员通过prometheus的告警发现CPU占用率一直处于很高的情况,通过top命令看到是由于Java程序引起的,希望能快速定位到是哪一部分代码导致了性能问题。

解决思路:

1、通过top –c 命令找到CPU占用率高的进程,获取它的进程ID。

在这里插入图片描述

2、使用top -p 进程ID单独监控某个进程,按H可以查看到所有的线程以及线程对应的CPU使用率,找到CPU使用率特别高的线程。

在这里插入图片描述

3、使用 jstack 进程ID 命令可以查看到所有线程正在执行的栈信息。使用 jstack 进程ID > 文件名 保存到文件中方便查看。

在这里插入图片描述

4、找到nid线程ID相同的栈信息,需要将之前记录下的十进制线程号转换成16进制。通过 printf ‘%x\n’ 线程ID 命令直接获得16进制下的线程ID。

在这里插入图片描述

5、找到栈信息对应的源代码,并分析问题产生原因。

案例2:接口响应时间很长的问题

问题:

在程序运行过程中,发现有几个接口的响应时间特别长,需要快速定位到是哪一个方法的代码执行过程中出现了性能问题。

解决思路:

已经确定是某个接口性能出现了问题,但是由于方法嵌套比较深,需要借助于arthas定位到具体的方法。

在这里插入图片描述

Arthas的trace命令

使用arthas的trace命令,可以展示出整个方法的调用路径以及每一个方法的执行耗时。

命令: trace 类名 方法名

  • 添加 --skipJDKMethod false 参数可以输出JDK核心包中的方法及耗时。
  • 添加 ‘#cost > 毫秒值’ 参数,只会显示耗时超过该毫秒值的调用。
  • 添加 –n 数值 参数,最多显示该数值条数的数据。
  • 所有监控都结束之后,输入stop结束监控,重置arthas增强的对象。

在这里插入图片描述

Arthas的watch命令

在使用trace定位到性能较低的方法之后,使用watch命令监控该方法,可以获得更为详细的方法信息。

命令: watch 类名 方法名 ‘{params, returnObj}’ ‘#cost>毫秒值’ -x 2

  • ‘{params, returnObj}‘ 代表打印参数和返回值。
  • -x 代表打印的结果中如果有嵌套(比如对象里有属性),最多只展开2层。允许设置的最大值为4。

在这里插入图片描述

总结:

1、通过arthas的trace命令,首先找到性能较差的具体方法,如果访问量比较大,建议设置最小的耗时,精确的找到耗时比较高的调用。

2、通过watch命令,查看此调用的参数和返回值,重点是参数,这样就可以在开发环境或者测试环境模拟类似的现象,通过debug找到具体的问题根源。

3、使用stop命令将所有增强的对象恢复。(因为arthas底层是使用动态代理的方式去增强这些对象,从而获取调用时间的。这样就增加了方法调用的开销,降低了性能)

案例3:定位偏底层的性能问题

问题:

有一个接口中使用了for循环向ArrayList中添加数据,但是最终发现执行时间比较长,需要定位是由于什么原因导致的性能低下。

解决思路:

Arthas提供了性能火焰图的功能,可以非常直观地显示所有方法中哪些方法执行时间比较长。

Arthas的profile命令

这个命令不支持windows版本,所以需要在linux上运行。

命令1: profiler start 开始监控方法执行性能

命令2: profiler stop --format html 以HTML的方式生成火焰图

火焰图中一般找绿色部分Java中栈顶上比较平的部分,很可能就是性能的瓶颈。

在这里插入图片描述

总结:

偏底层的性能问题,特别是由于JDK中某些方法被大量调用导致的性能低下,可以使用火焰图非常直观的找到原因。

这个案例中是由于创建ArrayList时没有手动指定容量,导致使用默认的容量而在添加对象过程中发生了多次的扩容,扩容需要将原来数组中的元素复制到新的数组中,消耗了大量的时间。通过火焰图可以看到大量的调用,修复完之后节省了20% ~ 50%的时间。

案例4:线程被耗尽问题

问题:

程序在启动运行一段时间之后,就无法接受任何请求了。将程序重启之后继续运行,依然会出现相同的情况。

解决思路:

线程耗尽问题,一般是由于执行时间过长,分析方法分成两步:

1、检测是否有死锁产生,无法自动解除的死锁会将线程永远阻塞。

2、如果没有死锁,再使用案例1的打印线程栈的方法检测线程正在执行哪个方法,一般这些大量出现的方法就是慢方法。

死锁:两个或以上的线程因为争夺资源而造成互相等待的现象。

解决方案:

线程死锁可以通过三种方法定位问题:

1、 jstack -l 进程ID > 文件名 将线程栈保存到本地。

在文件中搜索deadlock即可找到死锁位置:

在这里插入图片描述

2、 开发环境中使用visual vm或者Jconsole工具,都可以检测出死锁。使用线程快照生成工具就可以看到死锁的根源。生产环境的服务一般不会允许使用这两种工具连接。

在这里插入图片描述

3、 使用fastthread自动检测线程问题。

Fastthread和Gceasy类似,是一款在线的AI自动线程问题检测工具,可以提供线程分析报告。通过报告查看是否存在死锁问题。

在这里插入图片描述

更精细化的性能测试

在这里插入图片描述

Java程序在运行过程中,JIT即时编译器会实时对代码进行性能优化,所以仅凭少量的测试是无法真实反应运行系统最终给用户提供的性能。如下图,随着执行次数的增加,程序性能会逐渐优化。

在这里插入图片描述

OpenJDK中提供了一款叫JMH(Java Microbenchmark Harness)的工具,可以准确地对Java代码进行基准测试,量化方法的执行性能。

官网地址:https://github.com/openjdk/jmh

JMH会首先执行预热过程,确保JIT对代码进行优化之后再进行真正的迭代测试,最后输出测试的结果。

在这里插入图片描述

JMH环境搭建:

创建基准测试项目,在CMD窗口中,使用以下命令创建JMH环境项目:

mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test \
-Dversion=1.0

修改POM文件中的JDK版本号和JMH版本号,JMH最新版本号参考Github。

在这里插入图片描述

测试代码

//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class HelloWorldBench {@Benchmarkpublic int test1() {int i = 0;i++;return i;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(HelloWorldBench.class.getSimpleName()).resultFormat(ResultFormatType.JSON).forks(1).build();new Runner(opt).run();}
}
案例:日期格式化方法性能测试

问题:

在JDK8中,可以使用Date进行日期的格式化,也可以使用LocalDateTime进行格式化,使用JMH对比这两种格式化的性能。

解决思路:

1、搭建JMH测试环境。

2、编写JMH测试代码。

3、进行测试。

4、比对测试结果。

//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class DateBench {private static String sDateFormatString = "yyyy-MM-dd HH:mm:ss";private Date date = new Date();private LocalDateTime localDateTime = LocalDateTime.now();private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal();private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Setuppublic void setUp() {SimpleDateFormat sdf = new SimpleDateFormat(sDateFormatString);simpleDateFormatThreadLocal.set(sdf);}@Benchmarkpublic String date() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sDateFormatString);return simpleDateFormat.format(date);}@Benchmarkpublic String localDateTime() {return localDateTime.format(formatter);}@Benchmarkpublic String localDateTimeNotSave() {return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}@Benchmarkpublic String dateThreadLocal() {return simpleDateFormatThreadLocal.get().format(date);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(DateBench.class.getSimpleName()).resultFormat(ResultFormatType.JSON).forks(1).build();new Runner(opt).run();}
}

1、Date对象使用的SimpleDateFormatter是线程不安全的,所以每次需要重新创建对象或者将对象放入ThreadLocal中进行保存。其中每次重新创建对象性能比较差,将对象放入ThreadLocal之后性能相对还是比较好的。

2、LocalDateTime对象使用的DateTimeFormatter线程安全,并且性能较好,如果能将DateTimeFormatter对象保存下来,性能可以得到进一步的提升。

在这里插入图片描述

性能调优综合实战

问题:

小李的项目中有一个获取用户信息的接口性能比较差,他希望能对这个接口在代码中进行彻底的优化,提升性能。

解决思路:

1、使用trace分析性能瓶颈。

2、优化代码,反复使用trace测试性能提升的情况。

3、使用JMH在SpringBoot环境中进行测试。

4、比对测试结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据安全 | 期末复习(中)

文章目录 &#x1f4da;感知数据安全⭐️&#x1f407;传感器概述&#x1f407;传感器的静态特性&#x1f407;调制方式&#x1f407;换能攻击&#x1f407;现有防护策略 &#x1f4da;AI安全⭐️&#x1f407;智能语音系统——脆弱性&#x1f407;攻击手段&#x1f407;AI的两…

探索IOC和DI:解密Spring框架中的依赖注入魔法

IOC与DI的详细解析 IOC详解1 bean的声明2 组件扫描 DI详解 IOC详解 1 bean的声明 IOC控制反转&#xff0c;就是将对象的控制权交给Spring的IOC容器&#xff0c;由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。 要把某个对象交给IOC容器管理&#xff0c;需要在类上…

【深度学习】sdxl中的 text_encoder text_encoder_2 区别

镜像问题是&#xff1a;https://editor.csdn.net/md/?articleId135867689 代码仓库&#xff1a; https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/tree/main 截图&#xff1a; 为什么有两个CLIP编码器 text_encoder 和 text_encoder_2 &#xff1f; 在…

那些年与指针的爱恨情仇(一)---- 指针本质及其相关性质用法

关注小庄 顿顿解馋 (≧∇≦) 引言&#xff1a; 小伙伴们在学习c语言过程中是否因为指针而困扰&#xff0c;指针简直就像是小说女主&#xff0c;它逃咱追&#xff0c;我们插翅难飞…本篇文章让博主为你打理打理指针这个傲娇鬼吧~ 本节我们将认识到指针本质&#xff0c;何为指针和…

RHCE项目:使用LNMP搭建私有云存储

目录 一、准备工作 1、关闭防火墙、安全软件 2、搭建LNMP环境 3、上传软件 4、设置nextcloud安装命令权限 二、数据库 1、设置数据库 2、重启数据库 三、配置nginx 四、安装nextcloud 五、内网穿透 1、创建内网映射 2、linux系统安装花生壳客户端 3、重新打开浏览…

林浩然与极限的“无穷”约会

林浩然与极限的“无穷”约会 Lin Haoran’s Encounter with the Mathematical “Infinity” 在数学王国里&#xff0c;有一位名叫林浩然的大侠&#xff0c;他的江湖就是高等数学的殿堂。而他要挑战的终极Boss&#xff0c;便是那个既神秘又顽皮的“极限”。 In the kingdom of …

C# .Net6搭建灵活的RestApi服务器

1、准备 C# .Net6后支持顶级语句&#xff0c;更简单的RestApi服务支持&#xff0c;可以快速搭建一个极为简洁的Web系统。推荐使用Visual Studio 2022&#xff0c;安装"ASP.NET 和Web开发"组件。 2、创建工程 关键步骤如下&#xff1a; 包添加了“Newtonsoft.Json”&…

【Git】项目管理笔记

文章目录 本地电脑初始化docker报错.gitignoregit loggit resetgit statusgit ls-filesgit rm -r -f --cached拉取仓库文件更新本地的项目报错处理! [rejected] master -> master (fetch first)gitgitee.com: Permission denied (publickey).error: remote origin already e…

BACnet转IEC104网关BE104

随着电力系统信息化建设和数字化转型的进程不断加速&#xff0c;对电力能源的智能化需求也日趋增强。健全稳定的智慧电力系统能够为工业生产、基础设施建设以及国防建设提供稳定的能源支持。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&a…

研发日记,Matlab/Simulink避坑指南(六)——字节分割Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结归纳 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南&#xff08;一&#xff09;——Data Store Memory模块执行时序Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》…

Arduino开发实例-DRV8833电机驱动器控制直流电机

DRV8833电机驱动器控制直流电机 文章目录 DRV8833电机驱动器控制直流电机1、DRV8833电机驱动器介绍2、硬件接线图3、代码实现DRV8833 使用 MOSFET,而不是 BJT。 MOSFET 的压降几乎可以忽略不计,这意味着几乎所有来自电源的电压都会传递到电机。 这就是为什么 DRV8833 不仅比基…

寒假思维训练day15 牛客练习赛121

牛客练习赛ABCD题解&#xff0c;更新一个题解作为今天的任务收尾。 寒假思维训练day15 摘要&#xff1a; Part1&#xff1a;B题&#xff0c;B-You Brought Me A Gentle Breeze on the Field_牛客练习赛121 (nowcoder.com) Part2: C题&#xff0c;C-氧气少年的水滴 2_牛客练…

职言圈 | 小伙年薪95w,女朋友父母却爱搭不理,如今上岸国家电网,人不到,叔叔阿姨吃饭都不动筷子...

在职场中&#xff0c;每个人都经历着不同的起伏和挑战。有时候&#xff0c;我们会面临着职业生涯上的起伏&#xff0c;但正是这些经历让我们成长&#xff0c;让我们更加坚韧。 就像这位网友一样&#xff0c;他在过去的一年里经历了美团L8的职位和年薪95W&#xff0c;却面临着女…

Sentinel解密:SlotChain中的SLot大揭秘

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Sentinel解密&#xff1a;SlotChain中的SLot大揭秘 前言SlotChain简介&#xff1a;Sentinel的第一道防线入场仪式&#xff1a;SlotChain中的初始化SlotSlotChain的执行流程&#xff1a;从规则解析到流…

数据可视化练习

文章目录 试题示例 试题示例 绘制下图所示的表格 根据下表的数据&#xff0c;将班级名称一列作为x轴的刻度标签&#xff0c;将男生和女生两列的数据作为刻度标签对应的数值&#xff0c;使用bar()函数绘制下图所示的柱形图。 方式一 import numpy as np import matplotlib.p…

Kotlin快速入门系列3

Kotlin条件、循环控制 IF条件控制 与Java类似&#xff0c;一个if语句可包含布尔表达式和一条或多条语句。 fun compare(a:Int,b:Int) : Int{//常规传统用法var max aif (b>a) max breturn max//使用elsevar mMax : Intif(a>b){mMax a}else{mMax b}return mMax//使…

Unity中URP下额外灯的距离衰减

文章目录 前言一、额外灯的距离衰减二、DistanceAttenuation函数的传入参数1、distanceSqr2、distanceAndSpotAttenuation3、_AdditionalLightsAttenuation4、GetPunctualLightDistanceAttenuation函数三、DistanceAttenuation函数的程序体 前言 在上一篇文章中&#xff0c;我…

一个新的springboot-vue项目如何启动起来

一个新的springboot-vue项目如何启动起来 1.导入mysql 打开yml文件修改数据库密码 名称 用户名 2.打开pom.xml配置maven依赖 尽量换成自己使用过的版本号&#xff0c;或者打开中央仓库搜索相关内容版本号&#xff1a;https://central.sonatype.com/ 注解为黄色 说明工程…

《合成孔径雷达成像算法与实现》Figure5.17

clc clear close all距离向参数 R_eta_c 20e3; % 景中心斜距 Tr 25e-6; % 发射脉冲时宽 Kr 0.25e12; % 距离向调频率 Fr 7.5e6; % 距离向采样率 Nrg 256; % 距离线采样点数 Bw abs(Kr*Tr); …

@JsonIgnore的使用及相关问题的解决

目录 1 前言 2 对比及其使用方法 3 遇到的相关问题及解决方法 1 前言 在我们编写的后端项目中&#xff0c;有时候可能需要将某个实体类以JSON格式传送给前端&#xff0c;但是其中可能有部分内容我们并不想传送&#xff0c;这时候我们选择将这部分内容变成Null&#xff0c;这…