免费用鲲鹏资源!华为开发者空间实操:DevKit Java 性能分析,新手也能上手
华为开发者空间优势显著,提供免费的鲲鹏沙箱资源与云主机,能零成本支持开发者开展 Java 性能分析等开发工作。其鲲鹏 DevKit Java 性能分析工具功能强大,可图形化展示 Java 程序的堆、线程、锁等信息,助力快速定位死锁等性能瓶颈,还能给出优化建议,大幅提升开发效率。借助该空间,开发者能便捷完成从环境搭建到问题解决的全流程操作,是高效开展开发工作的理想选择,诚邀开发者前来体验。
1 概述
1.1 背景介绍
鲲鹏DevKit Java性能分析工具是一款针对鲲鹏平台的性能分析和调优工具,Java性能分析工具是针对基于鲲鹏的服务器上运行的Java程序的性能分析和优化工具,能图形化显示Java程序的堆、线程、锁、垃圾回收等信息,收集热点函数、定位程序瓶颈点,帮助用户采取针对性优。
1.2 适用对象
- 企业
- 个人开发者
- 高校学生
1.3 案例时间
本案例总时长预计40分钟。
1.4 案例流程
说明:
① 自动部署鲲鹏服务器;
② 安装鲲鹏Devkit插件;
③ 配置在线分析环境,通过浏览器访问鲲鹏服务器,添加目标节点;
④ 执行、编译死锁代码;
⑤ 死锁在线分析;
⑥ 修改代码,编译执行;
⑦ 在线分析。
1.5 资源总览
本案例预计花费总计0元。
资源名称 | 规格 | 单价(元) | 时长(h) |
---|---|---|---|
鲲鹏沙箱资源 | ECS/2核4GEIP/独享3M带宽EVS/通用型SSD 40G | 免费 | 1 |
云主机 | 2vCPUs | 4GB | 免费 | 1 |
利用鲲鹏DevKit剖析Java死锁问题的性能分析👈👈👈👈完整版 案例体验请点这里进行查看。
2 操作步骤
2.1 自动部署鲲鹏服务器
在云主机桌面右键选择“Open Terminal Here”,打开命令终端窗口。
执行自动部署命令如下:
hcd deploy --password 远端服务器密码 --time 3600
# --password 待部署项目所在ECS的root用户密码(至少8个字符)
# --time value 待部署资源的保留期(单位为秒,至少600秒,默认600秒)。当前案例预估需要40分钟,可以配置time为1小时保留期。
该命令会自动部署鲲鹏服务器。首次部署会直接执行,旧资源未到期时重复部署,会提示是否删除前面创建的资源,可以删除旧资源再次部署。
可以看到鲲鹏服务器链接:https://113.44.69.106:8084,表示部署成功,记录部署远端服务器公网IP,如截图中对应的就是:113.44.69.106 。
在开发者空间,我的开发资源下,找到鲲鹏沙箱环境,点击“使用详情”,可以看到我们刚刚自动部署的鲲鹏服务器,创建时间及资源释放时间等信息。
2.2 安装鲲鹏Devkit插件
云主机桌面单击鼠标右键,在菜单中选择“Open Terminal Here”打开终端。
通过ssh连接云服务器,如果有yes/no选择输入“yes”,然后输入 “云主机密码”,出现“Welcome to XXX”代表连接成功。
ssh root@云主机IP
输入地址“wget https://kunpeng-repoXXX”下载鲲鹏DevKit压缩包:
wget https://kunpeng-repo.obs.cn-north-4.myhuaweicloud.com/Kunpeng%20DevKit/Kunpeng%20DevKit%2024.0.RC3/DevKit-All-24.0.RC3-Linux-Kunpeng.tar.gz
稍作等待,下载完成执行“ls”可以看到下载的压缩包。
解压文件.
tar -zvxf DevKit-All-24.0.RC3-Linux-Kunpeng.tar.gz
进入文件夹,可以看到“install.sh”,执行安装。
cd DevKit-All-24.0.RC3-Linux-Kunpeng
sudo ./install.sh
如下图步骤,第一个默认回车即可。
出现如下图所示时选择“2”,去选择插件(1是安装全部,如选错可以“ctrl + c”停止,重复上面 “sudo ./install.sh”指令即可)。
本案例只用到java性能分析,选择“6”,后面选项根据提示操作,多数默认回车即可,直至安装成功。
安装成功可以看到“https://192.168.2.191:8086” 。
执行命令,修改端口。
如下命令的第一个ip为2.1自动部署的鲲鹏服务器IP,第二个ip为上图安装成功的IP。
iptables -t nat -A PREROUTING -d XX.XX.XX.XX -p tcp --dport 8086 -j DNAT --to-destination XX.XX.XX.XX:8086
2.3 配置在线分析环境
通过浏览器访问鲲鹏服务器,添加目标节点,以配置在线分析环境。
打开浏览器,输入“https://XX.XX.XX.XX:8086”(IP为2.1自动部署的鲲鹏服务器 IP),如果提示风险,点击接受并继续。
创建密码,并登录。
登录完成后添加目标环境:点击左上角“调优 添加”,在添加目标环境弹窗中输入:“用户名”,“云服务器密码”,点击“确定”;跳出来弹窗点击“确定”即可。
2.4 执行编译代码
云主机桌面单击鼠标右键,在菜单中选择“Open Terminal Here”打开终端。
通过ssh连接云服务器,如果有yes/no选择输入“yes”,然后输入 “云主机密码”,出现“Welcome to XXX”代表连接成功。
ssh root@云主机IP
进入到云主机后,执行如下命令查看当前云主机的JDK安装情况。
java -version
可以看到JDK默认是安装配置好的,输入“vim DeadLock.java”,回车进入,点击“i”进入编辑模式。
vim DeadLock.java
DeadLock.java 代码地址,如跳转不了请输入以下地址复制代码:
https://github.com/kunpengcompute/devkitdemo/blob/main/Hyper_tuner/testdemo/基于java性能分析工具的死锁调优实践/DeadLock.java
DeadLock.java代码如下(因格式可能存在问题建议进入链接进行复制):
public class DeadLock {
private static final Integer lockOne = new Integer(1);
private static final Integer lockTwo = new Integer(2);
public static void main(String[] args){
new Thread(()->{
try{
System.out.println("thread1 is running");
synchronized (lockOne){
System.out.println("thread1 get lock obj1");
Thread.sleep(1000L);
synchronized (lockTwo){
System.out.println("thread12 get lock obj2");
Thread.sleep(1000L);
}
}
} catch(InterruptedException e){
e.printStackTrace();
}
}).start();
new Thread(()->{
try{
System.out.println("thread2 is running");
synchronized (lockTwo){
System.out.println("thread2 get lock obj2");
Thread.sleep(1000L);
synchronized (lockOne){
System.out.println("thread2 get lock obj1");
Thread.sleep(1000L);
}
}
} catch(InterruptedException e){
e.printStackTrace();
}
}).start();
}
}
代码复制完毕,键盘点击“esc”,然后输入“:wq”保存退出。
javac DeadLock.java
java DeadLock
编译java文件,然后执行代码,可以看到执行成功。
2.5 死锁在线分析
进入浏览器界面,点击“调优”,点击“root@XXX”,点击DeadLock的“在线分析”。
对程序进行在线分析,在概览页签下观察每种状态的线程数,发现有两个线程处于阻塞状态,有死锁的嫌疑。
切换到CPU页签下的线程列表,找到阻塞状态的线程。观察一段时间发现发两个线程一直处于阻塞状态。
执行多次线程转储操作:在CPU下,点击“执行线程转储”,转储成功后点击“线程转储”下的锁分析图。发现两个阻塞中的线程发生了死锁。
选择CPU页签中线程转储下的原始数据,根据线程转储的原始数据得到死锁的相关信息。
点击优化建议的“查看详情”,查看死锁问题优化建议。
此时,我们已经获取到阻塞的原因及优化建议,点击“停止分析”。
2.6 修改代码,再次编译分析
找到阻塞的原因,接下来参考优化建议进行代码修改,解决死锁问题。
优化点解析说明:
- 共享变量的使用及操作
修改后的DeadLock类中定义了一个volatile修饰的共享变量i,并且在每个线程获取锁成功并执行完同步代码块后,会对i进行自增操作(i++)。而修改前的DeadLock类中没有这样的共享变量及相关操作。
private static volatile Integer i = 1;
原理说明:volatile关键字保证可见性
volatile关键字确保了共享变量i在多个线程之间的可见性。当一个线程修改了i的值,其他线程能够立即看到这个修改。这样,线程能够根据i的最新值来正确判断是否进入同步代码块获取锁,保证了整个机制的正确性。如果没有volatile关键字,线程可能会使用i的旧值,导致程序逻辑错误,无法有效避免死锁。
- 线程执行条件判断
在修改后的DeadLock类中,每个线程内部有一个while(true)循环,并且根据共享变量i的奇偶性来决定是否获取锁。当i为奇数时,第一个线程尝试获取锁;当i为偶数时,第二个线程尝试获取锁。而修改前DeadLock类中,两个线程启动后直接尝试获取锁,没有基于共享变量的条件判断。
while(true){
if(i % 2 == 1){
synchronized (lockOne){
System.out.println("thread1 get lock obj1");
Thread.sleep(1000L);
synchronized (lockTwo){
System.out.println("thread12 get lock obj2");
Thread.sleep(1000L);
}
}
i++;
}
}
while(true){
if(i % 2 == 0){
synchronized (lockOne){
System.out.println("thread1 get lock obj1");
Thread.sleep(1000L);
synchronized (lockTwo){
System.out.println("thread12 get lock obj2");
Thread.sleep(1000L);
}
}
i++;
}
}
原理说明:打破循环等待条件
在修改前DeadLock中,线程 1 先获取lockOne锁,然后等待lockTwo锁;而线程 2 先获取lockTwo锁,然后等待lockOne锁,形成了循环等待,导致死锁。
在修改后DeadLock中,通过引入共享变量i和条件判断,使得两个线程不会同时去竞争lockOne和lockTwo锁。例如,当i为奇数时,只有线程 1 会去尝试获取lockOne锁,线程 2 此时不会竞争lockOne锁,从而打破了循环等待的条件。当线程 1 获取锁并执行完同步代码块后,i变为偶数,此时线程 2 才有可能去获取lockOne锁,避免了死锁的发生。
回到终端,先停止程序:ctrl + z ,输入“vim DeadLock.java”,回车进入,点击“i”进入编辑模式。
vim DeadLock.java
修改后代码如下:
public class DeadLock {
private static final Integer lockOne = new Integer(1);
private static final Integer lockTwo = new Integer(2);
private static volatile Integer i = 1;
public static void main(String[] args){
new Thread(()->{
try{
System.out.println("thread1 is running");
while(true){
if(i % 2 == 1){
synchronized (lockOne){
System.out.println("thread1 get lock obj1");
Thread.sleep(1000L);
synchronized (lockTwo){
System.out.println("thread12 get lock obj2");
Thread.sleep(1000L);
}
}
i++;
}
}
} catch(InterruptedException e){
e.printStackTrace();
}
}).start();
new Thread(()->{
try{
System.out.println("thread2 is running");
while(true){
if(i % 2 == 0){
synchronized (lockOne){
System.out.println("thread1 get lock obj1");
Thread.sleep(1000L);
synchronized (lockTwo){
System.out.println("thread12 get lock obj2");
Thread.sleep(1000L);
}
}
i++;
}
}
} catch(InterruptedException e){
e.printStackTrace();
}
}).start();
}
}
代码变动部分请参考下图,代码修改完毕后,键盘点击“esc”,然后输入“:wq”保存退出。
编译java文件,然后执行代码:
javac DeadLock.java
java DeadLock
可以看到执行成功。运行效果如下:
回到浏览器,点击DeadLock 的“在线分析”。
查看优化后程序,调整两个线程持有锁的顺序后,程序不再发生死锁。
可以看到建议中数量少了一条CPU死锁已经不存在,问题已被解决。点击“停止分析”,终端停止代码。
总结:Java中的死锁问题一旦发生很难定位具体的代码位置,因为程序干扰因素比较多,所以涉及加锁解锁代码逻辑地方一定要仔细。建议如果涉及加锁的代码逻辑,程序是通过新起线程去执行或者是在线程池中执行,一定给线程设置有特定业务逻辑的名称,一旦发生问题也好定位。
在进行其他程序调优时,需要根据鲲鹏DevKit Java性能分析工具采集的实际结果和对应的优化建议进行调优操作。具体的调优思路可以参考本次实践。
至此,利用鲲鹏DevKit剖析Java死锁问题的性能分析实操全部结束。
更多推荐
所有评论(0)