Arthas,你真是Java程序员的大力丸

发布时间:2024年01月09日

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通
😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。
?? 3. Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当 。python爬虫入门进阶
?? 4. Ceph实战,从原理到实战应有尽有。 Ceph实战
?? 5. Java高并发编程入门,打卡学习Java高并发。 Java高并发编程入门

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]

image-20240103171507894

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状态。

image-20240103192316398

4.2. 定位CPU最高的方法

上面的代码例子有一个 CPU 空转的死循环,非常的消耗 CPU性能,那么怎么找出来呢?

使用 thread查看所有线程信息,同时会列出每个线程的 CPU 使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。

img

使用命令 thread 12 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数。

img

上面是通过先观察总体的线程信息,然后查看具体的线程运行情况,如果只是为了寻找CPU使用较高的线程,那么可以通过 thread -n[显示的线程个数],就可以排列出 CPU 使用率 Top N 的线程。

img

4.3. 线程死锁

在介绍线程死锁之前,首先回顾一下线程的几种常见状态:

  1. RUNNABLE : 线程正在运行状态
  2. TIMED_WAITIN:线程在等待运行中,调用以下方法会进入TIMED_WAITIN状态
    1. Thread#sleep()
    2. Object#wait() 并加了超时参数
    3. Thread#join() 并加了超时参数
    4. LockSupport#parkNanos()
    5. LockSupport#parkUntil()
  3. WAITIN:线程在等待运行中,调用以下方法会进入WAITIN状态
  4. Thread#sleep()
  5. Object#wait() 并加了超时参数
  6. Thread#join() 并加了超时参数
  7. LockSupport#parkNanos()
  8. LockSupport#parkUntil()
  9. 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();
	}

image-20240103195808453

4.4. 代码反编译

通过 jad ArthasTest 命令可以反编译代码。

image-20240103194444183

4.5. 查看方法信息:

通过sm命令可以查看类中的所有方法信息。

image-20240103194353959

4.6. 查看静态变量:

通过ognl ‘@ArthasTest@hashSet’ 命令可以查看 ArthasTest类的hashSet 静态变量的值。

image-20240103194000623

4.7. 查看方法的调用耗时

先定义一个测试的方法,这里定义了UserController类以及UserServiceImpl类,UserController类作为接口的总入口。

import java.util.HashMap;

@RestController
@Slf4j
public class UserController {

	@Autowired
	private 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

image-20240103204500535

image-20240103205037173

image-20240103204943468

参考:

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
文章来源:https://blog.csdn.net/u014534808/article/details/135481255
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。