Arthas - Java 线上问题定位处理的终极利器-深蓝源码网


时间: 2020-09-03 00:08:26 人气: 2304 评论: 0

前言

在使用 Arthas 之前,当遇到 Java 线上问题时,如 CPU 飙升、负载突高、内存溢出等问题,你需要查命令,查网络,然后 jps、jstack、jmap、jhat、jstat、hprof 等一通操作。最终焦头烂额,还不一定能查出问题所在。而现在,大多数的常见问题你都可以使用 Arthas 轻松定位,迅速解决,及时止损,准时下班。

1、Arthas 介绍

ArthasAlibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,提供 Tab 自动不全,可以方便的定位和诊断线上程序运行问题。截至本篇文章编写时,已经收获 Star 17000+。

Arthas 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 Github 的项目里的 Issues 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。

开源地址:https://github.com/alibaba/arthas

官方文档:https://alibaba.github.io/arthas

2、Arthas 使用场景

得益于 Arthas 强大且丰富的功能,让 Arthas 能做的事情超乎想象。下面仅仅列举几项常见的使用情况,更多的使用场景可以在熟悉了 Arthas 之后自行探索。

  1. 是否有一个全局视角来查看系统的运行状况?
  2. 为什么 CPU 又升高了,到底是哪里占用了 CPU ?
  3. 运行的多线程有死锁吗?有阻塞吗?
  4. 程序运行耗时很长,是哪里耗时比较长呢?如何监测呢?
  5. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  6. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  7. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  8. 有什么办法可以监控到 JVM 的实时运行状态?

3、Arthas 怎么用

前文已经提到,Arthas 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。

3.1 安装

可以在官方 Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee 下载。

# 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 只是一个 java 程序,所以可以直接用 java -jar 运行。运行时或者运行之后要选择要监测的 Java 进程。

# 运行方式1,先运行,在选择 Java 进程 PID
java -jar arthas-boot.jar
# 选择进程(输入[]内编号(不是PID)回车)
[INFO] arthas-boot version: 3.1.4
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 11616 com.Arthas
  [2]: 8676
  [3]: 16200 org.jetbrains.jps.cmdline.Launcher
  [4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer

# 运行方式2,运行时选择 Java 进程 PID
java -jar arthas-boot.jar [PID]

查看 PID 的方式可以通过 ps 命令,也可以通过 JDK 提供的 jps命令。

# 查看运行的 java 进程信息
$ jps -mlvV 
# 筛选 java 进程信息
$ jps -mlvV | grep [xxx]

jps 筛选想要的进程方式。

在出现 Arthas Logo 之后就可以使用命令进行问题诊断了。下面会详细介绍。

更多的启动方式可以参考 help 帮助命令。

# 其他用法
EXAMPLES:
  java -jar arthas-boot.jar <pid>
  java -jar arthas-boot.jar --target-ip 0.0.0.0
  java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
  java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
  java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
--agent-id bvDOe8XbTM2pQWjF4cfw
  java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'
  java -jar arthas-boot.jar -c 'sysprop; thread' <pid>
  java -jar arthas-boot.jar -f batch.as <pid>
  java -jar arthas-boot.jar --use-version 3.1.4
  java -jar arthas-boot.jar --versions
  java -jar arthas-boot.jar --session-timeout 3600
  java -jar arthas-boot.jar --attach-only
  java -jar arthas-boot.jar --repo-mirror aliyun --use-http

3.3 web console

Arthas 目前支持 Web Console,在成功启动连接进程之后就已经自动启动,可以直接访问 http://127.0.0.1:8563/ 访问,页面上的操作模式和控制台完全一样。

3.4 常用命令

下面列举一些 Arthas 的常用命令,看到这里你可能还不知道怎么使用,别急,后面会一一介绍。

命令

介绍

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 的启动方式,下面会依据一些情况,详细说一说 Arthas 的使用方式。在使用命令的过程中如果有问题,每个命令都可以是 -h 查看帮助信息。

首先编写一个有各种情况的测试类运行起来,再使用 Arthas 进行问题定位,

import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 * Arthas Demo
 * 公众号:未读代码
 *
 * @Author niujinpeng
 */
@Slf4j
public class Arthas {

    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) {
                log.info("cpu start 100");
            }
        });
        // 添加到线程
        executorService.submit(thread);
    }

    /**
     * 普通消耗CPU的线程
     */
    private static void cpuNormal() {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    log.info("cpu start");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    /**
     * 模拟线程阻塞,向已经满了的线程池提交线程
     */
    private static void thread() {
        Thread thread = new Thread(() -> {
            while (true) {
                log.debug("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) {
                log.info(Thread.currentThread() + " get ResourceA");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info(Thread.currentThread() + "waiting get resourceB");
                synchronized (resourceB) {
                    log.info(Thread.currentThread(
						 技术沙龙 教程文章 热点综合					

评论