您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通
😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。
❤️ 3. Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当 。python爬虫入门进阶
❤️ 4. Ceph实战,从原理到实战应有尽有。 Ceph实战
❤️ 5. Java高并发编程入门,打卡学习Java高并发。 Java高并发编程入门
文章目录
- 1. Arthas是什么
- 2. Arthas的使用场景
- 2.1 性能问题定位
- 2.2 实时调试
- 2.3 内存分析
- 2.4 线上问题快速定位
- 3. Arthas怎么使用
- 3.1 下载安装
- 3.2 运行Arthas
- 3.4 使用Arthas命令
- **3.5 退出**
- 4. Arthas的常用操作
- 4.1. 全局监控
- 4.2. 定位CPU最高的方法
- 4.3. 线程死锁
- 4.4. 代码反编译
- 4.5. 查看方法信息:
- 4.6. 查看静态变量:
- 4.7. 查看方法的调用耗时
1. Arthas是什么
Arthas(阿尔萨斯)是一款由阿里巴巴开源团队开发的Java应用性能监控与诊断工具。它作为一种开源的Java诊断工具,主要用于在生产环境中实时监控、分析和诊断Java应用程序的性能问题。Arthas提供了一系列的命令行工具,可以实时查看Java应用的运行状态、堆栈信息、方法执行耗时等关键性能数据,帮助开发者快速定位并解决问题。
2. Arthas的使用场景
Arthas适用于各种Java应用场景,特别是在生产环境中解决实时性能问题。以下是一些常见的使用场景:
2.1 性能问题定位
Arthas可以实时监控应用程序的性能数据,包括方法执行时间、线程状态等,帮助开发者快速定位潜在的性能瓶颈。
2.2 实时调试
在生产环境中,使用Arthas可以实时进行代码调试,查看变量的值、修改变量的值,甚至动态加载类,而不需要重启应用。
2.3 内存分析
Arthas支持对Java应用的内存进行实时分析,帮助开发者查找内存泄漏、优化内存使用等问题。
2.4 线上问题快速定位
Arthas可以在生产环境中实时诊断问题,无需重启应用,从而快速定位线上故障,提高故障排查效率。
3. Arthas怎么使用
Arthas的使用相对简单,主要通过命令行工具进行操作。以下是一些基本的使用步骤:
3.1 下载安装
首先,通过官方网站或Maven仓库下载Arthas,并解压到本地目录。
官方文档:https://alibaba.github.io/arthas
到官方的开源地址:https://github.com/alibaba/arthas,或者国内的Gitee地址下去下载arthas的jar包。
# github下载
wget https://alibaba.github.io/arthas/arthas-boot.jar
# 或者 Gitee 下载
wget https://arthas.gitee.io/arthas-boot.jar
# 打印帮助信息
java -jar arthas-boot.jar -h
3.2 运行Arthas
Arthas 只是一个 java 程序,所以可以直接用 java -jar
运行。运行时或者运行之后要选择要监测的 Java 进程。
# 运行方式1,先运行,在选择 Java 进程 PID
java -jar arthas-boot.jar
# 选择进程(输入[]内编号(不是PID)回车)
[INFO] arthas-boot version: 3.5.0
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 24480 com.Arthas[2]: 20520 com.jay.demos.ArthasTest[3]: 16200 org.jetbrains.jps.cmdline.Launcher[4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer# 运行方式2,运行时选择 Java 进程 PID
java -jar arthas-boot.jar [PID]
3.4 使用Arthas命令
一旦连接成功,可以使用各种Arthas命令进行实时监控、诊断,例如:dashboard
查看仪表盘、trace
追踪方法调用、watch
监控变量等。下面列举一些常用命令。
命令 | 介绍 |
---|---|
dashboard | 当前系统的实时数据面板 |
thread | 查看当前 JVM 的线程堆栈信息 |
watch | 方法执行数据观测 |
trace | 方法内部调用路径,并输出方法路径上的每个节点上耗时 |
stack | 输出当前方法被调用的调用路径 |
tt | 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 |
monitor | 方法执行监控 |
jvm | 查看当前 JVM 信息 |
vmoption | 查看,更新 JVM 诊断相关的参数 |
sc | 查看 JVM 已加载的类信息 |
sm | 查看已加载类的方法信息 |
jad | 反编译指定已加载类的源码 |
classloader | 查看 classloader 的继承树,urls,类加载信息 |
heapdump | 类似 jmap 命令的 heap dump 功能 |
3.5 退出
使用 shutdown 退出时 Arthas 同时自动重置所有增强过的类 。
4. Arthas的常用操作
上面已经了解了Arthas的使用场景以及启动方式,下面就来说说Arthas的使用方式。
首先,编写一个有各种异常场景的代码。这个代码模拟了CPU过高,线程阻塞,线程死锁,内存不断被消耗等场景。
将代码上传到Linux服务器上,通过 1. javac ArthasTest.java
命令编译将ArthasTest.java文件编译成ArthasTest.class文件。接着通过 java ArthasTest
命令来运行此文件。
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** <p>* Arthas Demo* 公众号:码农飞哥** @Author 码农飞哥*/public class ArthasTest {private static HashSet hashSet = new HashSet();/*** 线程池,大小1*/private static ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String[] args) {// 模拟 CPU 过高cpu();// 模拟线程阻塞thread();// 模拟线程死锁deadThread();// 不断的向 hashSet 集合增加数据addHashSetThread();}/*** 不断的向 hashSet 集合添加数据*/public static void addHashSetThread() {// 初始化常量new Thread(() -> {int count = 0;while (true) {try {hashSet.add("count" + count);Thread.sleep(10000);count++;} catch (InterruptedException e) {e.printStackTrace();}}}).start();}public static void cpu() {cpuHigh();cpuNormal();}/*** 极度消耗CPU的线程*/private static void cpuHigh() {Thread thread = new Thread(() -> {while (true) {System.out.println("极度消耗CPU的线程,任务死循环,cpu start 100");}});// 添加到线程executorService.submit(thread);}/*** 普通消耗CPU的线程*/private static void cpuNormal() {for (int i = 0; i < 10; i++) {new Thread(() -> {while (true) {System.out.println("普通消耗CPU的线程,任务睡眠3000毫秒,死循环,cpu start");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}/*** 模拟线程阻塞,向已经满了的线程池提交线程*/private static void thread() {Thread thread = new Thread(() -> {while (true) {System.out.println("模拟线程阻塞,thread start");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});// 添加到线程executorService.submit(thread);}/*** 死锁*/private static void deadThread() {/** 创建资源 */Object resourceA = new Object();Object resourceB = new Object();// 创建线程Thread threadA = new Thread(() -> {synchronized (resourceA) {System.out.println(Thread.currentThread() + " get ResourceA");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resourceB");synchronized (resourceB) {System.out.println(Thread.currentThread() + " get resourceB");}}});Thread threadB = new Thread(() -> {synchronized (resourceB) {System.out.println(Thread.currentThread() + " get ResourceB");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resourceA");synchronized (resourceA) {System.out.println(Thread.currentThread() + " get resourceA");}}});threadA.start();threadB.start();}
}
4.1. 全局监控
首先通过 dashboard 命令查看当前系统的实时数据面板。结果如下,我们看到有两个线程的状态是BLOCKED状态。
4.2. 定位CPU最高的方法
上面的代码例子有一个 CPU
空转的死循环,非常的消耗 CPU性能
,那么怎么找出来呢?
使用 thread查看所有线程信息,同时会列出每个线程的 CPU
使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。
使用命令 thread 12 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数。
上面是通过先观察总体的线程信息,然后查看具体的线程运行情况,如果只是为了寻找CPU使用较高的线程,那么可以通过 thread -n[显示的线程个数]
,就可以排列出 CPU 使用率 Top N 的线程。
4.3. 线程死锁
在介绍线程死锁之前,首先回顾一下线程的几种常见状态:
- RUNNABLE : 线程正在运行状态
- TIMED_WAITIN:线程在等待运行中,调用以下方法会进入TIMED_WAITIN状态
- Thread#sleep()
- Object#wait() 并加了超时参数
- Thread#join() 并加了超时参数
- LockSupport#parkNanos()
- LockSupport#parkUntil()
- WAITIN:线程在等待运行中,调用以下方法会进入WAITIN状态
- Thread#sleep()
- Object#wait() 并加了超时参数
- Thread#join() 并加了超时参数
- LockSupport#parkNanos()
- LockSupport#parkUntil()
- BLOCKED 阻塞,等待锁
上面的模拟代码里,定义了线程池大小为1 的线程池,然后在 cpuHigh
方法里提交了一个线程,在 thread
方法再次提交了一个线程,后面的这个线程因为线程池已满,会阻塞下来。
使用 thread | grep pool 命令查看线程池里线程信息。
上面的模拟代码里 deadThread
方法实现了一个死锁,使用 thread -b 命令查看直接定位到死锁信息。
/*** 死锁*/private static void deadThread() {/** 创建资源 */Object resourceA = new Object();Object resourceB = new Object();// 创建线程Thread threadA = new Thread(() -> {synchronized (resourceA) {System.out.println(Thread.currentThread() + " get ResourceA");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resourceB");synchronized (resourceB) {System.out.println(Thread.currentThread() + " get resourceB");}}});Thread threadB = new Thread(() -> {synchronized (resourceB) {System.out.println(Thread.currentThread() + " get ResourceB");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resourceA");synchronized (resourceA) {System.out.println(Thread.currentThread() + " get resourceA");}}});threadA.start();threadB.start();}
4.4. 代码反编译
通过 jad ArthasTest 命令可以反编译代码。
4.5. 查看方法信息:
通过sm命令可以查看类中的所有方法信息。
4.6. 查看静态变量:
通过ognl ‘@ArthasTest@hashSet’ 命令可以查看 ArthasTest类的hashSet 静态变量的值。
4.7. 查看方法的调用耗时
先定义一个测试的方法,这里定义了UserController类以及UserServiceImpl类,UserController类作为接口的总入口。
import java.util.HashMap;@RestController
@Slf4j
public class UserController {@Autowiredprivate UserServiceImpl userService;@GetMapping(value = "/user")public HashMap<String, Object> getUser(Integer uid) throws Exception {log.info("------模拟用户查询,uid={}----", uid);// 模拟用户查询userService.get(uid);HashMap<String, Object> hashMap = new HashMap<>();hashMap.put("uid", uid);hashMap.put("name", "name" + uid);return hashMap;}
}
UserServiceImpl类作为业务实现。
package com.jay.demos.web;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class UserServiceImpl {public void get(Integer uid) throws Exception {check(uid);service(uid);redis(uid);mysql(uid);}public void service(Integer uid) throws Exception {int count = 0;for (int i = 0; i < 10; i++) {count += i;}log.info("service end {}", count);}public void redis(Integer uid) throws Exception {int count = 0;for (int i = 0; i < 10000; i++) {count += i;}log.info("redis end {}", count);}public void mysql(Integer uid) throws Exception {long count = 0;for (int i = 0; i < 10000000; i++) {count += i;}log.info("mysql end {}", count);}public boolean check(Integer uid) throws Exception {if (uid == null || uid < 0) {log.error("uid不正确,uid:{}", uid);throw new Exception("uid不正确");}return true;}
}
如果要通过一个接口中,各部分的耗时,则可以使用: trace [类地址] [方法名] 。例如本例中的就是 trace com.jay.demos.web.UserController getUser
参考:
https://cloud.tencent.com/developer/article/1534894
【昵称】码农飞哥
【城市】合肥
【个人介绍】
1.某厂高级 Java 开发,8 年开发经验
2. 老徐八年合伙人|飞巴三年合伙人|小林三年合伙人|AI 破局会员
3. CSDN 博客专家,全网 15 万粉丝,累计阅读量228万
4. 写技术类商业征文两年GMV六位数
5. 卖毕业设计单月变现 2 万,CSDN付费专栏变现1万
CSDN平台:https://blog.csdn.net/u014534808
#公众号:码农飞哥
【可提供资源】
1.社群,朋友圈推广 3000+好友(CSDN博主粉丝居多)
2.GPT、Calude、MJ 等大部分 AI 工具的用法、信息和资源。
3.CSDN 涨粉经验,付费专栏编写经验
4.Java 和 Python 软件的开发能力
5.多个长期合作的广告主想与我交流的小伙伴可以可以点击下方二维码加我 WX: wei158556