hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

Arthas - Java 线上题目定位处置惩罚的最终利器

2019-11-18杂谈搜奇网48°c
A+ A-

媒介

在运用 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() + " get resourceB");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (resourceB) {
                log.info(Thread.currentThread() + " get ResourceB");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info(Thread.currentThread() + "waiting get resourceA");
                synchronized (resourceA) {
                    log.info(Thread.currentThread() + " get resourceA");
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

4.1 全局监控

运用 dashboard 敕令能够概览顺序的 线程、内存、GC、运转环境信息。

4.2 CPU 为何起飞了

上面的代码例子有一个 CPU 空转的死循环,非常的斲丧 CPU机能,那末怎样找出来呢?

运用 thread检察一切线程信息,同时会列出每一个线程的 CPU 运用率,能够看到图里 ID 为12 的线程 CPU 运用100%。

运用敕令 thread 12 检察 CPU 斲丧较高的 12 号线程信息,能够看到 CPU 运用较高的要领和行数(这里的行数能够和上面代码里的行数有区分,由于上面的代码在我写文章时刻从新排过版了)。

上面是先经由过程视察整体的线程信息,然后检察详细的线程运转状况。假如只是为了寻觅 CPU 运用较高的线程,能够直接运用敕令 thread -n [显现的线程个数] ,就能够排列出 CPU 运用率 Top N 的线程。

定位到的 CPU 运用最高的要领。

4.3 线程池线程状况

定位线程题目之前,先回忆一下线程的几种罕见状况:

  • RUNNABLE 运转中
  • TIMED_WAITIN 挪用了以下要领的线程会进入TIMED_WAITING
    1. Thread#sleep()
    2. Object#wait() 并加了超时参数
    3. Thread#join() 并加了超时参数
    4. LockSupport#parkNanos()
    5. LockSupport#parkUntil()
  • WAITING 当线程挪用以下要领时会进入WAITING状况:
    1. Object#wait() 而且不加超时参数
    2. Thread#join() 而且不加超时参数
    3. LockSupport#park()
  • BLOCKED 壅塞,守候锁

上面的模仿代码里,定义了线程池大小为1 的线程池,然后在 cpuHigh 要领里提交了一个线程,在 thread要领再次提交了一个线程,背面的这个线程由于线程池已满,会壅塞下来。

运用 thread | grep pool 敕令检察线程池里线程信息。

能够看到线程池有 WAITING 的线程。

4.4 线程死锁

上面的模仿代码里 deadThread要领完成了一个死锁,运用 thread -b 敕令检察直接定位到死锁信息。

/**
 * 死锁
 */
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() + " get resourceB");
            }
        }
    });

    Thread threadB = new Thread(() -> {
        synchronized (resourceB) {
            log.info(Thread.currentThread() + " get ResourceB");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info(Thread.currentThread() + "waiting get resourceA");
            synchronized (resourceA) {
                log.info(Thread.currentThread() + " get resourceA");
            }
        }
    });
    threadA.start();
    threadB.start();
}

检查到的死锁信息。

4.5 反编译

上面的代码放到了包 com下,假定这是一个线程环境,当疑心当前运转的代码不是本身想要的代码时,能够直接反编译出代码,也能够挑选性的检察类的字段或要领信息。

假如疑心不是本身的代码,能够运用 jad 敕令直接反编译 class。

jad 敕令还供应了一些其他参数:

# 反编译只显现源码
jad --source-only com.Arthas
# 反编译某个类的某个要领
jad --source-only com.Arthas mysql

4.6 检察字段信息

运用 sc -d -f 敕令检察类的字段信息。

[arthas@20252]$ sc -d -f com.Arthas
sc -d -f com.Arthas
 class-info        com.Arthas
 code-source       /C:/Users/Niu/Desktop/arthas/target/classes/
 name              com.Arthas
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       Arthas
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
                     +-sun.misc.Launcher$ExtClassLoader@2ef1e4fa
 classLoaderHash   18b4aac2
 fields            modifierfinal,private,static
                   type    org.slf4j.Logger
                   name    log
                   value   Logger[com.Arthas]

                   modifierprivate,static
                   type    java.util.HashSet
                   name    hashSet
                   value   [count1, count2]

                   modifierprivate,static
                   type    java.util.concurrent.ExecutorService
                   name    executorService
                   value   java.util.concurrent.ThreadPoolExecutor@71c03156[Ru
                           nning, pool size = 1, active threads = 1, queued ta
                           sks = 0, completed tasks = 0]


Affect(row-cnt:1) cost in 9 ms.

4.7 检察要领信息

运用 sm 敕令检察类的要领信息。

[arthas@22180]$ sm com.Arthas
com.Arthas <init>()V
com.Arthas start()V
com.Arthas thread()V
com.Arthas deadThread()V
com.Arthas lambda$cpuHigh$1()V
com.Arthas cpuHigh()V
com.Arthas lambda$thread$3()V
com.Arthas addHashSetThread()V
com.Arthas cpuNormal()V
com.Arthas cpu()V
com.Arthas lambda$addHashSetThread$0()V
com.Arthas lambda$deadThread$4(Ljava/lang/Object;Ljava/lang/Object;)V
com.Arthas lambda$deadThread$5(Ljava/lang/Object;Ljava/lang/Object;)V
com.Arthas lambda$cpuNormal$2()V
Affect(row-cnt:16) cost in 6 ms.

4.8 对变量的值非常猎奇

运用 ognl 敕令,ognl 表达式能够轻松操纵想要的信息。

代码照样上面的示例代码,我们检察变量 hashSet 中的数据:

检察静态变量 hashSet 信息。

[arthas@19856]$ ognl '@com.Arthas@hashSet'
@HashSet[
    @String[count1],
    @String[count2],
    @String[count29],
    @String[count28],
    @String[count0],
    @String[count27],
    @String[count5],
    @String[count26],
    @String[count6],
    @String[count25],
    @String[count3],
    @String[count24],

检察静态变量 hashSet 大小。

[arthas@19856]$ ognl '@com.Arthas@hashSet.size()'
    @Integer[57]

以至能够举行操纵。

[arthas@19856]$ ognl  '@com.Arthas@hashSet.add("test")'
    @Boolean[true]
[arthas@19856]$
# 检察增加的字符
[arthas@19856]$ ognl  '@com.Arthas@hashSet' | grep test
    @String[test],
[arthas@19856]$

ognl 能够做许多事变,能够参考 ognl 表达式特别用法( https://github.com/alibaba/arthas/issues/71 )。

4.9 顺序有无题目

4.9.1 运转较慢、耗时较长

运用 trace 敕令能够跟踪统计要领耗时

此次换一个模仿代码。一个最基本的 Springboot 项目(固然,不想 Springboot 的话,你也能够直接在 UserController 里 main 要领启动)掌握层 getUser 要领挪用了 userService.get(uid);,这个要领中离别举行checkserviceredismysql操纵。

@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    @GetMapping(value = "/user")
    public HashMap<String, Object> getUser(Integer uid) throws Exception {
        // 模仿用户查询
        userService.get(uid);
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("uid", uid);
        hashMap.put("name", "name" + uid);
        return hashMap;
    }
}

模仿代码 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;
     }
}

运转 Springboot 以后,运用 trace== 敕令最先检测耗时状况。

[arthas@6592]$ trace com.UserController getUser

接见接口 /getUser ,能够看到耗时信息,看到 com.UserServiceImpl:get()要领耗时较高。

继承跟踪耗时高的要领,然后再次接见。

[arthas@6592]$ trace com.UserServiceImpl get

很清晰的看到是 com.UserServiceImplmysql要领耗时是最高的。

Affect(class-cnt:1 , method-cnt:1) cost in 31 ms.
`---ts=2019-10-16 14:40:10;thread_name=http-nio-8080-exec-8;id=1f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@23a918c7
    `---[6.792201ms] com.UserServiceImpl:get()
        +---[0.008ms] com.UserServiceImpl:check() #17
        +---[0.076ms] com.UserServiceImpl:service() #18
        +---[0.1089ms] com.UserServiceImpl:redis() #19
        `---[6.528899ms] com.UserServiceImpl:mysql() #20

4.9.2 统计要领耗时

运用 monitor 敕令监控统计要领的实行状况。

每5秒统计一次 com.UserServiceImpl 类的 get 要领实行状况。

monitor -c 5 com.UserServiceImpl get

4.10 想视察要领信息

下面的示例用到了文章的前两个模仿代码。

4.10.1 视察要领的入参出参信息

运用 watch 敕令轻松检察输入输出参数以及非常等信息。

 USAGE:
   watch [-b] [-e] [-x <value>] [-f] [-h] [-n <value>] [-E] [-M <value>] [-s] class-pattern method-pattern express [condition-express]

 SUMMARY:
   Display the input/output parameter, return object, and thrown exception of specified method invocation
   The express may be one of the following expression (evaluated dynamically):
           target : the object
            clazz : the object's class
           method : the constructor or method
           params : the parameters array of method
     params[0..n] : the element of parameters array
        returnObj : the returned object of method
         throwExp : the throw exception of method
         isReturn : the method ended by return
          isThrow : the method ended by throwing exception
            #cost : the execution time in ms of method invocation
 Examples:
   watch -b org.apache.commons.lang.StringUtils isBlank params
   watch -f org.apache.commons.lang.StringUtils isBlank returnObj
   watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2
   watch -bf *StringUtils isBlank params
   watch *StringUtils isBlank params[0]
   watch *StringUtils isBlank params[0] params[0].length==1
   watch *StringUtils isBlank params '#cost>100'
   watch -E -b org\.apache\.commons\.lang\.StringUtils isBlank params[0]

 WIKI:
   https://alibaba.github.io/arthas/watch

经常使用操纵:

# 检察入参和出参
$ watch com.Arthas addHashSet '{params[0],returnObj}'
# 检察入参和出参大小
$ watch com.Arthas addHashSet '{params[0],returnObj.size}'
# 检察入参和出参中是不是包括 'count10'
$ watch com.Arthas addHashSet '{params[0],returnObj.contains("count10")}'
# 检察入参和出参,出参 toString
$ watch com.Arthas addHashSet '{params[0],returnObj.toString()}'

检察入参出参。

检察返回的非常信息。

4.10.2 视察要领的挪用途径

运用 stack敕令检察要领的挪用信息。

# 视察 类com.UserServiceImpl的 mysql 要领挪用途径
stack com.UserServiceImpl mysql

4.10.3 要领挪用时空隧道

运用 tt 敕令纪录要领实行的细致状况。

tt 敕令要领实行数据的时空隧道,纪录下指定要领每次挪用的入参和返回信息,并能对这些差别的时刻下挪用举行视察 。

经常使用操纵:

最先纪录要领挪用信息:tt -t com.UserServiceImpl check

能够看到纪录中 INDEX=1001 的纪录的 IS-EXP = true ,申明此次挪用涌现非常。

检察纪录的要领挪用信息: tt -l

检察挪用纪录的细致信息(-i 指定 INDEX): tt -i 1001

能够看到 INDEX=1001 的纪录的非常信息。

从新提议挪用,运用指定纪录,运用 -p 从新挪用。

tt -i 1001 -p

文中代码已上传到 Github。

https://github.com/niumoo/lab-notes/tree/master/src/main/java/net/codingme/arthas

本文作者: 未读代码
我的微信:wn8398
个人主页: https://www.codingme.net
本篇文章是博主原创文章,迎接转载,转载时在显著位置说明原文链接即可。
关注民众号复兴资本能够猎取Java 中心学问整顿&口试材料。
  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
Arthas - Java 线上题目定位处置惩罚的最终利器

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282371.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>