文章目录
- 版权声明
- 案例1:CPU占用率高问题
- 问题描述
- 解决思路
- 补充内容
- 案例2:接口响应时间长问题
- 问题描述
- 解决思路
- Arthas trace命令
- Arthas watch命令
- 解决问题
- 案例3:定位偏底层性能问题
- 问题描述
- 解决思路:Arthas火焰图
- 问题解决
- 案例4:线程被耗尽问题
- 问题描述
- 解决思路
- 问题解决
- 死锁代码优化
版权声明
- 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。
- 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的内容可能会随着时间的推移而过时或需要更新。
- 若您是黑马程序员或相关权利人,如有任何侵犯版权的地方,请您及时联系我,我将立即予以删除或进行必要的修改。
- 对于其他读者,请在阅读本博客内容时保持遵守相关法律法规和道德准则,谨慎参考,并自行承担因此产生的风险和责任。
- 本博客中的部分观点和意见仅代表我个人,不代表黑马程序员的立场。
- 由于作者精力有限,有关代码的演示和工具的使用和操作,请食用官方的B站JVM教程视频
案例1:CPU占用率高问题
问题描述
- 监控人员通过prometheus的告警发现CPU占用率一直处于很高的情况,通过top命令看到是由于Java程序引起的,希望能快速定位导致性能问题的代码。
解决思路
- 通过
top –c
命令找到CPU占用率高的进程,获取进程ID
- 使用
top -p 进程ID
单独监控某个进程,按 H H H查看到所有的线程以及线程对应的CPU使用率,找到CPU使用率特别高的线程,并记录线程ID。
- 使用
jstack 进程ID
命令可以查看到所有线程正在执行的栈信息。使用jstack 进程ID > 文件名
保存到文件中方便查看。
- 找到
n
i
d
线程
I
D
nid线程ID
nid线程ID相同的栈信息,需要将之前记录下的十进制线程号转换成16进制。通过
p
r
i
n
t
f
′
printf '%x\n'
printf′ 线程ID 命令直接获得16进制下的线程ID
- 找到栈信息对应的源代码,并分析问题产生原因
补充内容
- 在定位CPU占用率高的问题时,需要关注的是状态为
R
U
N
N
A
B
L
E
RUNNABLE
RUNNABLE的线程。但实际上,有一些线程执行本地方法时并不会消耗CPU,而只是在等待。但 JVM 仍然会将它们标识成“RUNNABLE”状态。
案例2:接口响应时间长问题
问题描述
- 程序运行过程中,发现有几个接口的响应时间特别长,需要快速定位执行过程中出现性能问题的代码。
解决思路
- 确定出现性能问题的方法,借助于arthas定位到具体的方法(在方法嵌套比较深的情况下)
Arthas trace命令
- 使用arthas的trace命令,可以展示出整个方法的调用路径以及每一个方法的执行耗时。
- 语法格式
trace 类名 方法名
⚫ 添加--skipJDKMethod false
参数可以输出JDK核心包中的方法及耗时。
⚫ 添加 #cost > 毫秒值
参数,只会显示耗时超过该毫秒值的调用。
⚫ 添加 –n 数值
参数,最多显示该数值条数的数据。
⚫ 所有监控都结束之后,输入
s
t
o
p
stop
stop结束监控,重置arthas增强的对象
Arthas watch命令
- 使用trace定位到性能较低的方法后,使用watch命令监控该方法,可以获得更为详细的方法信息。
- 语法格式
watch 类名 方法名 ‘{params, returnObj}’ ‘#cost>毫秒值' -x 2
{params, returnObj}
代表打印参数和返回值。-x
代表打印的结果中如果有嵌套(比如对象里有属性),最多只展开2层。允许设置的最大值为4。
解决问题
- 通过arthas的trace命令,首先找到性能较差的具体方法,如果访问量比较大,建议设置最小的耗时,精确的找到耗时比较高的调用。
- 通过watch命令,查看此调用的参数和返回值,关注参数,在开发环境或者测试环境模拟现象,通过debug找到具体的问题根源。
- 使用stop命令将所有增强的对象恢复。
案例3:定位偏底层性能问题
问题描述
- 接口中使用for循环向ArrayList中添加数据,但是最终发现执行时间比较长,需要定位是导致的性能低下的原因
@GetMapping("/profile1")
public void test6() throws InterruptedException {
ArrayList<Integer> objects = new ArrayList<>();
for (Integer i = 0; i < 20000000; i++) {
objects.add(i);
}
}
解决思路:Arthas火焰图
- 使用Arthas提供性能火焰图的功能,查看方法的执行耗时
- 使用arthas的profile命令,生成性能监控的火焰图
profiler start #1 开始监控方法执行性能 profiler stop --format html #2 以HTML的方式生成火焰图
- 火焰图中一般找绿色部分Java中栈顶上比较平的部分,很可能就是性能的瓶颈
- 偏底层的性能问题,特别是由于JDK中某些方法被大量调用导致的性能低下,可以使用火焰图非常直观的找到原因
问题解决
- 案例中是由于创建ArrayList时没有手动指定容量,导致使用默认的容量而在添加对象过程中发生了多次的扩容,扩容需要将原来数组中的元素复制到新的数组中,消耗了大量的时间。
- 优化后的代码
@GetMapping("/profile2")
public void test7() throws InterruptedException {
ArrayList<Integer> objects = new ArrayList<>(20000000);
for (Integer i = 0; i < 20000000; i++) {
objects.add(i);
}
}
案例4:线程被耗尽问题
问题描述
- 程序在启动运行一段时间之后,就无法接受任何请求。将程序重启之后继续运行,依然会出现相同的情况。
解决思路
- 线程耗尽问题,一般是由于执行时间过长,分析方法分成两步:
- 检测是否有死锁产生,无法自动解除的死锁会将线程永远阻塞。
- 如果没有死锁,再使用案例1的打印线程栈的方法检测线程正在执行的方法,一般大量出现的方法就是慢方法。
- 死锁:两个或以上的线程因为争夺资源而造成互相等待的现象
问题解决
线程死锁可以通过三种方法定位问题:
jstack -l 进程ID > 文件名
将线程栈保存到本地,在文件中搜索deadlock即可找到死锁位置
- 开发环境中使用visual vm或者Jconsole工具,检测死锁。使用线程快照生成工具查看死锁的根源。生产环境的服务一般不会允许使用这两种工具连接。
- 使用fastthread自动检测线程问题(Fastthread,是一款在线的AI自动线程问题检测工具,可以提供线程分析报告。通过报告查看是否存在死锁问题。)
死锁代码优化
- 问题代码
private Object obj1 = new Object();
private Object obj2 = new Object();
@GetMapping("/deadlock1")
public String test1() throws InterruptedException {
synchronized (obj1){
Thread.sleep(5000);
synchronized (obj2){
return "返回成功";
}
}
@GetMapping("/deadlock2")
public String test2() throws InterruptedException {
synchronized (obj2){
Thread.sleep(5000);
synchronized (obj1){
return "返回成功";
}
}
- 优化代码
private Object obj1 = new Object();
private Object obj2 = new Object();
private Lock lock1 = new ReentrantLock();
private Lock lock2 = new ReentrantLock();
@GetMapping("/deadlock1")
public String test1() throws InterruptedException {
boolean b1 = lock1.tryLock(1, TimeUnit.SECONDS);
if(b1){
try {
Thread.sleep(5000);
boolean b2 = lock2.tryLock(1, TimeUnit.SECONDS);
if(b2){
try{
return "返回成功";
}
finally {
lock2.unlock();
}
}
}finally {
lock1.unlock();
}
}
return "处理失败";
}
@GetMapping("/deadlock2")
public String test2() throws InterruptedException {
boolean b1 = lock2.tryLock(1, TimeUnit.SECONDS);
if(b1){
try {
Thread.sleep(5000);
boolean b2 = lock1.tryLock(1, TimeUnit.SECONDS);
if(b2){
try{
return "返回成功";
}
finally {
lock1.unlock();
}
}
}finally {
lock2.unlock();
}
}
return "处理失败";
}