阿里面试:Arthas原理和使用,大概说说吧?

发布时间:2024年01月12日

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

Arthas原理和使用,大概说下吧?

Arthas 也是大家定位和解决线上问题,非常常用的一个工具。

所以,这个题目如果答不上来, 说明平时没怎么解决过线上问题, 面试基本就挂。

所以,这道题目,非常重要。

这里,尼恩把这道面试题以及参考答案,做了详细的梳理。 大家可以收藏起来, 时不时看看,做到温故而知新。

同时,也收入咱们的 《尼恩Java面试宝典PDF》V159版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】取

文章目录

Arthas 在线排错工具的巨大价值

Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。

通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。

更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。

Arthas 旨在解决这些问题。开发人员可以通过Arthas 在线解决生产问题。通过Arthas ,无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。

Arthas工具

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

Arthas中集成了大部分JDK工具的功能实现,因此,在线上情况时,可以通过它快速的帮助我们解决问题,如CPU占用过高、线程阻塞、死锁、代码动态修改、方法执行缓慢、排查404等。

Arthas 官方文档

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到JVM的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从JVM内查找某个类的实例?

Arthas支持JDK6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。

阿里提供的在线的Arthas Terminal学习方式(Arthas Tutorials (aliyun.com)),帮助大家快速上手。

arthas快速开始

方式一:使用 arthas-boot 启动arthas

推荐使用arthas-boot(推荐),下载arthas-boot.jar,然后用java -jar的方式启动:

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息:

java -jar arthas-boot.jar -h

arthas-boot启动过程中, 会下载一些依赖包,如果下载速度比较慢,可以使用aliyun的镜像:

java -jar arthas-boot.jar --repo-mirror aliyun --use-http

arthas-boot 完成依赖下载,并且启动。

启动之后,这时候,arthas会列出当前检测到的java进程,

Arthas会将本机中所有的Java进程查询出来,类似于jps/ps的作用:

上面的例子中, 有一个 java进程, Arthas 自己的进程

30162  Arthas.jar

如果你的机器中启动了多个Java应用,此时会查询出来一个应用列表。

要操作那个进程,我们可以根据前面的序号进行输入。

输入选择自己要操作的Java应用,如上情况中,再输入1即可,并按回车键即可;

$ 1

最终,Arthas成功启动,接下来再通过Arthas提供的指令进行操作即可:

方式二:使用 as.sh 脚本启动arthas

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,有个专门的install.sh脚本

请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:

curl -L https://arthas.aliyun.com/install.sh | sh

上述命令会下载启动脚本文件 install.sh,并且下载 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在shell下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息。

arthas官方使用案例:

可以通过下面的方式自己动手实践。

1、启动 math-game

math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
math-game源代码:查看

curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar

2、启动 arthas

在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
  • 执行该程序的用户需要和目标进程具有相同的权限。

    比如以admin用户来执行:sudo su admin && java -jar arthas-boot.jarsudo -u admin -EH java -jar arthas-boot.jar

  • 如果 attach 不上目标进程,可以查看~/logs/arthas/ 目录下的日志。

  • 如果下载速度比较慢,可以使用 aliyun 的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http

  • java -jar arthas-boot.jar -h 打印更多参数信息。

选择应用 java 进程:

$ $ java -jar arthas-boot.jar
* [1]: 35542
  [2]: 71560 math-game.jar

math-game进程是第 2 个,则输入 2,再输入回车/enter

Arthas 会 attach 到目标进程上,并输出日志:

[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'


wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24

$

3、dashboard 展示当前进程的信息

输入 dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。

$ dashboard
ID     NAME                   GROUP          PRIORI STATE  %CPU    TIME   INTERRU DAEMON
17     pool-2-thread-1        system         5      WAITIN 67      0:0    false   false
27     Timer-for-arthas-dashb system         10     RUNNAB 32      0:0    false   true
11     AsyncAppender-Worker-a system         9      WAITIN 0       0:0    false   true
9      Attach Listener        system         9      RUNNAB 0       0:0    false   true
3      Finalizer              system         8      WAITIN 0       0:0    false   true
2      Reference Handler      system         10     WAITIN 0       0:0    false   true
4      Signal Dispatcher      system         9      RUNNAB 0       0:0    false   true
26     as-command-execute-dae system         10     TIMED_ 0       0:0    false   true
13     job-timeout            system         9      TIMED_ 0       0:0    false   true
1      main                   main           5      TIMED_ 0       0:0    false   false
14     nioEventLoopGroup-2-1  system         10     RUNNAB 0       0:0    false   false
18     nioEventLoopGroup-2-2  system         10     RUNNAB 0       0:0    false   false
23     nioEventLoopGroup-2-3  system         10     RUNNAB 0       0:0    false   false
15     nioEventLoopGroup-3-1  system         10     RUNNAB 0       0:0    false   false
Memory             used   total max    usage GC
heap               32M    155M  1820M  1.77% gc.ps_scavenge.count  4
ps_eden_space      14M    65M   672M   2.21% gc.ps_scavenge.time(m 166
ps_survivor_space  4M     5M    5M           s)
ps_old_gen         12M    85M   1365M  0.91% gc.ps_marksweep.count 0
nonheap            20M    23M   -1           gc.ps_marksweep.time( 0
code_cache         3M     5M    240M   1.32% ms)
Runtime
os.name                Mac OS X
os.version             10.13.4
java.version           1.8.0_162
java.home              /Library/Java/JavaVir
                       tualMachines/jdk1.8.0
                       _162.jdk/Contents/Hom
                       e/jre

4、thread 命令Main Class

通过 thread 命令来获取到math-game进程的 Main Class

thread 1会打印线程 ID 1 的栈,通常是 main 函数的线程。

$ thread 1 | grep 'main('
    at demo.MathGame.main(MathGame.java:17)

得到了 Main Class 的全路径名称 demo.MathGame

5、jad命令 来反编译 Main Class

jad 全路径名称, 进行源码的反编译

$ jad demo.MathGame

ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
  +-sun.misc.Launcher$ExtClassLoader@66350f69

Location:
/tmp/math-game.jar

/*
 * Decompiled with CFR 0_132.
 */
package demo;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class MathGame {
    private static Random random = new Random();
    private int illegalArgumentCount = 0;

    public static void main(String[] args) throws InterruptedException {
        MathGame game = new MathGame();
        do {
            game.run();
            TimeUnit.SECONDS.sleep(1L);
        } while (true);
    }

    public void run() throws InterruptedException {
        try {
            int number = random.nextInt();
            List<Integer> primeFactors = this.primeFactors(number);
            MathGame.print(number, primeFactors);
        }
          .......
        return result;
    }
    
   public List<Integer> primeFactors(int number) {
        if (number < 2) {
            ++this.illegalArgumentCount;
            throw new IllegalArgumentException("number is: " + number + ", need >= 2");
        }
        ArrayList<Integer> result = new ArrayList<Integer>();
        int i = 2;
        while (i <= number) {
            if (number % i == 0) {
                result.add(i);
                number /= i;
                i = 2;
                continue;
            }
            ++i;
        }
        return result;
    }
}

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

6、watch

通过 watch命令来查看到指定函数的调用情况。

watch能观察到的范围为:返回值抛出异常入参,通过编写 OGNL 表达式进行对应变量的查看。

watch 命令 的手册地址

例子,通过 watch命令来查看 demo.MathGame#primeFactors 函数的返回值:

$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
    @Integer[5],
    @Integer[47],
    @Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
    @Integer[2],
    @Integer[5],
    @Integer[317],
    @Integer[503],
    @Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
    @Integer[2],
    @Integer[2],
    @Integer[3],
    @Integer[3],
    @Integer[31],
    @Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
    @Integer[5],
    @Integer[29],
    @Integer[7651739],
]

更多的功能可以查看进阶教程在新窗口打开

7、退出 arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出 arthas,可以执行stop命令。

Arthas的核心命令详解

Arthas从最初的发布开始,随着后续社区的活跃性增强及用户群体的不断壮大,指令也越发完善与丰富,至目前为止提供了基础命令、JVM命令、class命令以及字节码增强命令等几大类。

1、基础命令

  • base64 - base64 编码转换,和 linux 里的 base64 命令类似
  • cat - 打印文件内容,和 linux 里的 cat 命令类似
  • cls - 清空当前屏幕区域
  • echo - 打印参数,和 linux 里的 echo 命令类似
  • grep - 匹配查找,和 linux 里的 grep 命令类似
  • help - 查看命令帮助信息
  • history - 打印命令历史
  • keymap - Arthas 快捷键列表及自定义快捷键
  • pwd - 返回当前的工作目录,和 linux 命令类似
  • quit - 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
  • reset - 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
  • session - 查看当前会话的信息
  • stop - 关闭 Arthas 服务端,所有 Arthas 客户端全部退出
  • tee - 复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似
  • version - 输出当前目标 Java 进程所加载的 Arthas 版本号

2、类操作命令

2.1 sc 命令:

sc “Search-Class” 的简写,查看JVM已加载的类信息,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,

可选项如下:

  • class-pattern:类名表达式匹配(必填),如sc java.lang.String
  • -E:开启正则表达式匹配,默认为通配符匹配。
  • -c:指定class的类加载器的哈希码。
  • -d:显示当前类的详细信息,包含来源、声明、类加载相关等信息。
  • -f:输出当前类的属性成员信息,与-d一同使用。
  • -x:指定输出静态变量时属性的遍历深度,默认为0
  • -n:具有详细信息的匹配类的最大数量(默认为100)。

提示

class-pattern 支持全限定名,支持两种格式:

  • com.taobao.test.AAA,

  • 也支持 com/taobao/test/AAA 这样的格式,

这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦。

sc 命令 使用参考
  • 模糊搜索
$ sc demo.*
demo.MathGame
Affect(row-cnt:1) cost in 55 ms.
  • 打印类的详细信息
$ sc -d demo.MathGame
class-info        demo.MathGame
code-source       /private/tmp/math-game.jar
name              demo.MathGame
isInterface       false
isAnnotation      false
isEnum            false
isAnonymousClass  false
isArray           false
isLocalClass      false
isMemberClass     false
isPrimitive       false
isSynthetic       false
simple-name       MathGame
modifier          public
annotation
interfaces
super-class       +-java.lang.Object
class-loader      +-sun.misc.Launcher$AppClassLoader@3d4eac69
                  +-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash   3d4eac69

Affect(row-cnt:1) cost in 875 ms.
  • 打印出类的 Field 信息
  $ sc -d -f demo.MathGame
  class-info        demo.MathGame
  code-source       /private/tmp/math-game.jar
  name              demo.MathGame
  isInterface       false
  isAnnotation      false
  isEnum            false
  isAnonymousClass  false
  isArray           false
  isLocalClass      false
  isMemberClass     false
  isPrimitive       false
  isSynthetic       false
  simple-name       MathGame
  modifier          public
  annotation
  interfaces
  super-class       +-java.lang.Object
  class-loader      +-sun.misc.Launcher$AppClassLoader@3d4eac69
                      +-sun.misc.Launcher$ExtClassLoader@66350f69
  classLoaderHash   3d4eac69
  fields            modifierprivate,static
                    type    java.util.Random
                    name    random
                    value   java.util.Random@522b4
                            08a
  
                    modifierprivate
                    type    int
                    name    illegalArgumentCount
  
  
  Affect(row-cnt:1) cost in 19 ms.
2.2 sm 命令

“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。

sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。

sm:查看已加载类的方法信息,可选项如下:

  • class-pattern:类名表达式匹配(必填),如sm java.lang.String
  • -E:开启正则表达式匹配,默认为通配符匹配。
  • -d:查看方法的详细信息,配合方法名使用,如sm -d java.lang.String toString
  • -c:同sc -c作用相同。
  • -n:同sc -h作用相同。
参数说明
参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
[d]展示每个方法的详细信息
[E]开启正则表达式匹配,默认为通配符匹配
[c:]指定 class 的 ClassLoader 的 hashcode
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[n:]具有详细信息的匹配类的最大数量(默认为 100)
使用参考
$ sm java.lang.String
java.lang.String-><init>
java.lang.String->equals
java.lang.String->toString
java.lang.String->hashCode
java.lang.String->compareTo
java.lang.String->indexOf
java.lang.String->valueOf
.......
Affect(row-cnt:44) cost in 1342 ms.
$ sm -d java.lang.String toString
 declaring-class  java.lang.String
 method-name      toString
 modifier         public
 annotation
 parameters
 return           java.lang.String
 exceptions

Affect(row-cnt:1) cost in 3 ms.
2.3 jad命令

jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;如需批量下载指定包的目录的 class 字节码可以参考 dump

  • 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便

  • 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解

jad:反编译指定已加载类的源码,可选项如下:

  • -c、-E都与前面的作用相同,举几个案例演示用法。
  • jad --source-only java.lang.String:只显示反编译后的Java源码。
  • jad java.lang.String:反编译指定类。
  • jad java.lang.String toString:反编译指定类的某个方法。
参数说明参数说明
参数名称参数说明
class-pattern类名表达式匹配
[c:]类所属 ClassLoader 的 hashcode
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[E]开启正则表达式匹配,默认为通配符匹配

jad 全路径名称, 进行源码的反编译

前面有例子

$ jad demo.MathGame

ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
  +-sun.misc.Launcher$ExtClassLoader@66350f69

Location:
/tmp/math-game.jar

/*
 * Decompiled with CFR 0_132.
 */
package demo;

...
public class MathGame {
    private static Random random = new Random();
    private int illegalArgumentCount = 0;
.....
}

Affect(row-cnt:1) cost in 970 ms.
2.4 mc命令

Memory Compiler/内存编译器,编译.java文件生成.class

mc:内存编译器,编译.java源文件为.class类文件,可选项如下:

  • -c:指定类加载器(以哈希码的方式指定)。
  • -d:指定编译后的类文件输出位置。
2.6 retransform命令

加载外部的.class文件,retransform (再转化;插桩) jvm 已加载的类, 比如在线替换 类的方法。

retransform:起到 热部署的作用,用于线上替换类方法。

注意点:

  • ①重新替换JVM中被加载的类时,不能新增方法或属性。
  • ②正在执行的方法不能替换。

retransform 使用参考

   retransform /tmp/Test.class
   retransform -l
   retransform -d 1                    # delete retransform entry
   retransform --deleteAll             # delete all retransform entries
   retransform --classPattern demo.*   # triger retransform classes
   retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
   retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
2.7 dump命令

dump`:导出已加载类的字节码数据到指定目录

dump 命令将 JVM 中实际运行的 class 的 byte code dump 到指定目录,适用场景批量下载指定包目录的 class 字节码;如需反编译单一类、实时查看类信息,可参考 jad

dump命令可选项如下:

  • -c、-E作用与之前的相同。
  • -d:指定输出的路径,如dump -d /usr/data/byteCode java.lang.String
参数说明
参数名称参数说明
class-pattern类名表达式匹配
[c:]类所属 ClassLoader 的 hashcode
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[d:]设置类文件的目标目录
[E]开启正则表达式匹配,默认为通配符匹配
使用参考
$ dump java.lang.String
 HASHCODE  CLASSLOADER  LOCATION
 null                   /Users/admin/logs/arthas/classdump/java/lang/String.class
Affect(row-cnt:1) cost in 119 ms.
2.8 classloader命令

classloader:查类加载器的继承树,urls,类加载信息,

classloader 命令将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。

可以让指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。对于ResourceNotFoundException比较有用。

classloader 可选项如下:

  • -a:显示所有类加载器加载的所有类。
  • -c:查看指定的类加载器的加载路径,如classloader -c 14ae5a5
  • -l:统计每个类加载器的加载信息。
  • -r:查找某个的资源路径,配合-c使用,如classloader -c 33909752 -r java/lang/String.class
  • -t:以树结构列出每个类加载器之间的父子关系。
  • -u:显示类加载器的url统计信息,如加载总数、父子关系、加载范围等。
  • -i:查看每种类加载器的实例数量及其加载总量。
使用参考

按类加载类型查看统计信息

$ classloader
 name                                       numberOfInstances  loadedCountTotal
 com.taobao.arthas.agent.ArthasClassloader  1                  2115
 BootstrapClassLoader                       1                  1861
 sun.reflect.DelegatingClassLoader          5                  5
 sun.misc.Launcher$AppClassLoader           1                  4
 sun.misc.Launcher$ExtClassLoader           1                  1
Affect(row-cnt:5) cost in 3 ms.

按类加载实例查看统计信息

$ classloader -l
 name                                                loadedCount  hash      parent
 BootstrapClassLoader                                1861         null      null
 com.taobao.arthas.agent.ArthasClassloader@68b31f0a  2115         68b31f0a  sun.misc.Launcher$ExtClassLoader@66350f69
 sun.misc.Launcher$AppClassLoader@3d4eac69           4            3d4eac69  sun.misc.Launcher$ExtClassLoader@66350f69
 sun.misc.Launcher$ExtClassLoader@66350f69           1            66350f69  null
Affect(row-cnt:4) cost in 2 ms.

查看 ClassLoader 的继承树

$ classloader -t
+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@66350f69
  +-com.taobao.arthas.agent.ArthasClassloader@68b31f0a
  +-sun.misc.Launcher$AppClassLoader@3d4eac69
Affect(row-cnt:4) cost in 3 ms.

查看 URLClassLoader 实际的 urls

$ classloader -c 3d4eac69
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar

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

注意 hashcode 是变化的,需要先查看当前的 ClassLoader 信息,提取对应 ClassLoader 的 hashcode。

对于只有唯一实例的 ClassLoader 可以通过 class name 指定,使用起来更加方便:

$ classloader --classLoaderClass sun.misc.Launcher$AppClassLoader
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar

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

使用 ClassLoader 去查找 resource

$ classloader -c 3d4eac69  -r META-INF/MANIFEST.MF
 jar:file:/System/Library/Java/Extensions/MRJToolkit.jar!/META-INF/MANIFEST.MF
 jar:file:/private/tmp/math-game.jar!/META-INF/MANIFEST.MF
 jar:file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar!/META-INF/MANIFEST.MF

也可以尝试查找类的 class 文件:

$ classloader -c 1b6d3586 -r java/lang/String.class
 jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/String.class

使用 ClassLoader 去加载类

$ classloader -c 3d4eac69 --load demo.MathGame
load class success.
 class-info        demo.MathGame
 code-source       /private/tmp/math-game.jar
 name              demo.MathGame
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       MathGame
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@3d4eac69
                     +-sun.misc.Launcher$ExtClassLoader@66350f69
 classLoaderHash   3d4eac69

3、JVM操作的命令

3.1 dashboard命令

dashboard:当前系统的实时数据面板,资源监控仪表盘,

dashboard 包含线程、内存、GC、运行环境等信息, 按 ctrl+c 退出。

dashboard可选项如下:

  • -i:刷新实时数据的间隔时间,默认为5000ms
  • -n:刷新实时数据的次数,默认为一直持续刷新,按ctrl+c退出。

参数说明

参数名称参数说明
[i:]刷新实时数据的时间间隔 (ms),默认 5000ms
[n:]刷新实时数据的次数
使用参考
$ dashboard
ID   NAME                           GROUP           PRIORITY   STATE     %CPU      DELTA_TIME TIME      INTERRUPTE DAEMON
-1   C2 CompilerThread0             -               -1         -         1.55      0.077      0:8.684   false      true
53   Timer-for-arthas-dashboard-07b system          5          RUNNABLE  0.08      0.004      0:0.004   false      true
22   scheduling-1                   main            5          TIMED_WAI 0.06      0.003      0:0.287   false      false
-1   C1 CompilerThread0             -               -1         -         0.06      0.003      0:2.171   false      true
-1   VM Periodic Task Thread        -               -1         -         0.03      0.001      0:0.092   false      true
49   arthas-NettyHttpTelnetBootstra system          5          RUNNABLE  0.02      0.001      0:0.156   false      true
16   Catalina-utility-1             main            1          TIMED_WAI 0.0       0.000      0:0.029   false      false
-1   G1 Young RemSet Sampling       -               -1         -         0.0       0.000      0:0.019   false      true
17   Catalina-utility-2             main            1          WAITING   0.0       0.000      0:0.025   false      false
34   http-nio-8080-ClientPoller     main            5          RUNNABLE  0.0       0.000      0:0.016   false      true
23   http-nio-8080-BlockPoller      main            5          RUNNABLE  0.0       0.000      0:0.011   false      true
-1   VM Thread                      -               -1         -         0.0       0.000      0:0.032   false      true
-1   Service Thread                 -               -1         -         0.0       0.000      0:0.006   false      true
-1   GC Thread#5                    -               -1         -         0.0       0.000      0:0.043   false      true
Memory                     used     total    max      usage    GC
heap                       36M      70M      4096M    0.90%    gc.g1_young_generation.count   12
g1_eden_space              6M       18M      -1       33.33%                                  86
g1_old_gen                 30M      50M      4096M    0.74%    gc.g1_old_generation.count     0
g1_survivor_space          491K     2048K    -1       24.01%   gc.g1_old_generation.time(ms)  0
nonheap                    66M      69M      -1       96.56%
codeheap_'non-nmethods'    1M       2M       5M       22.39%
metaspace                  46M      47M      -1       98.01%
Runtime
os.name                                                        Mac OS X
os.version                                                     10.15.4
java.version                                                   15
java.home                                                      /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home
systemload.average                                             10.68
processors                                                     8
uptime                                                         272s
数据说明
  • ID: Java 级别的线程 ID,注意,这个 ID 不能跟 jstack 中的 nativeID 一一对应。
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10 之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程的 cpu 使用率。比如采样间隔 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率=100/1000=10%
  • DELTA_TIME: 上次采样之后线程运行增量 CPU 时间,数据格式为
  • TIME: 线程运行总 CPU 时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是 daemon 线程
JVM 内部线程

Java 8 之后支持获取 JVM 内部线程 CPU 时间,这些线程只有名称和 CPU 时间,没有 ID 及状态等信息(显示 ID 为-1)。

JVM 内部线程包括下面几种:

  • JIT 编译线程: 如 C1 CompilerThread0, C2 CompilerThread0
  • GC 线程: 如GC Thread0, G1 Young RemSet Sampling
  • 其它内部线程: 如VM Periodic Task Thread, VM Thread, Service Thread

通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 情况,方便了解 JVM 整体运行状况。

  • 当 JVM 堆(heap)/元数据(metaspace)空间不足或 OOM 时,可以看到 GC 线程的 CPU 占用率明显高于其他的线程。
  • 当执行trace/watch/tt/redefine等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时, 清除了此 class 相关的 JIT 编译结果,需要重新编译。
3.2 thread命令

查看当前线程信息,查看线程的堆栈

thread:查看当前线程的堆栈信息,可选项如下:

  • -n:显示最活跃的n条线程信息,如thread -n 5
  • -i:指定活跃性统计的采样间隔时间,如thread -i 5000
  • -b:自动检测出应用中当前阻塞其他线程的线程。
  • --state:查询目前程序中处于指定状态的线程,如thread --state BLOCKED
  • id:查看某个线程的详细信息,如thread 21

参数列表

参数名称参数说明
id线程 id
[n:]指定最忙的前 N 个线程并打印堆栈
[b]找出当前阻塞其他线程的线程
[i <value>]指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200
[–all]显示所有匹配的线程
cpu 使用率是如何统计出来的?

这里的 cpu 使用率与 linux 命令top -H -p <pid> 的线程%CPU类似,一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例。

工作原理说明:

  • 首先第一次采样,获取所有线程的 CPU 时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime()sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()接口)
  • 然后睡眠等待一个间隔时间(默认为 200ms,可以通过-i指定间隔时间)
  • 再次第二次采样,获取所有线程的 CPU 时间,对比两次采样数据,计算出每个线程的增量 CPU 时间
  • 线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%

注意

注意: 这个统计也会产生一定的开销(JDK 这个接口本身开销比较大),因此会看到 as 的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如 5000 毫秒。

eg:支持一键展示当前最忙的前 N 个线程并打印堆栈:
$ thread -n 3
"C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms


"arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
    at java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)
    at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
    at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
    at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
    at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
    at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base@11.0.7/java.lang.Thread.run(Thread.java:834)


"VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
  • 没有线程 ID,包含[Internal]表示为 JVM 内部线程,参考dashboard命令的介绍。
  • cpuUsage为采样间隔时间内线程的 CPU 使用率,与dashboard命令的数据一致。
  • deltaTime为采样间隔时间内线程的增量 CPU 时间,小于 1ms 时被取整显示为 0ms。
  • time 线程运行总 CPU 时间。

注意:线程栈为第二采样结束时获取,不能表明采样间隔时间内该线程都是在处理相同的任务。建议间隔时间不要太长,可能间隔时间越大越不准确。 可以根据具体情况尝试指定不同的间隔时间,观察输出结果。

thread --all, 显示所有匹配的线程

显示所有匹配线程信息,有时需要获取全部 JVM 的线程数据进行分析

3.3 jvm命令

jvm:查看JVM信息,包含线程/内存/OS/内存结构/编译/类加载/运行环境等信息。

使用参考,请参见 官方文档

jvm | arthas (aliyun.com)

3.4 sysprop命令

查看当前 JVM 的系统属性(System Property)

sysprop java.home

使用参考,请参见 官方文档

sysprop | arthas (aliyun.com)

3.5 sysenv命令

sysenv:,查看当前JVM的环境参数。

使用参考,请参见 官方文档

sysenv | arthas (aliyun.com)

3.6 vmoption命令

vmoption:查看或修改JVM的运行时参数,如:

  • vmoption PrintGC:查看PrintGC是否开启。
  • vmoption PrintGC true:更改PrintGC参数。

使用参考,请参见 官方文档

vmoption | arthas (aliyun.com)

3.7 getstatic命令

getstatic:查看类的静态属性,用法:getstatic class_nmae field_name

使用参考,请参见 官方文档

getstatic | arthas (aliyun.com)

3.8 ognl命令

ognl:执行ognl表达式

使用参考,请参见 官方文档

ognl | arthas (aliyun.com)

3.9 heapdump命令

heapdump:类似于jmap工具的堆dump功能,使用方式:

  • heapdump /usr/data/dump/heap.hprof:导出堆快照到指定文件。
  • heapdump --live /usr/data/dump/heap.hprof:只导出存活对象的快照。

使用参考,请参见 官方文档

heapdump | arthas (aliyun.com)

3.10 mbean命令

mbean:查看Mbean的信息

使用参考,请参见 官方文档

mbean | arthas (aliyun.com)

3.11 memory命令

memory:查看JVM的内存划分、内存结构以及占用率。

使用参考,请参见 官方文档

memory | arthas (aliyun.com)

4、字节码增强命令

4.1 watch命令

让你能方便的观察到指定函数的调用情况。能观察到的范围为:返回值抛出异常入参,通过编写 OGNL 表达式进行对应变量的查看。

使用参考,请参见 官方文档

watch | arthas (aliyun.com)

watch:观测指定方法的执行情况,可选项如下:

  • -b:在方法调用之前观测。
  • -s:在方法成功执行后观测。
  • -e:在方法异常执行后观测。
  • -f:在方法结束后进行观测(默认)。
  • -n:指定观测的次数。
  • 使用示例:watch -s -n 10 demo.MathGame primeFactors
4.2 tt 命令

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。

这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。

于是乎,TimeTunnel 命令就诞生了。

tt

tt 全称 TimeTunnel ,意思是 : 方法执行数据的时空隧道,

记录指定方法每次执行的数据,并能在不同的时间下调用观测,

记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

使用参考,请参见 官方文档

tt | arthas (aliyun.com)

tt命令可选项如下:

  • <class_pattern> <method_pattern>:指定要观测的类名+方法名。
  • -t:记录下方法每次执行的情况,如tt -t demo.MathGame primeFactors
  • -i <index>:查看某条执行记录的执行详情,如tt -i 1000
  • -d <index>:删除某条执行记录,配合-i使用,tt- d -i 1000
  • -n:设置执行次数,如tt -t -n 10 demo.MathGame primeFactors
  • -l:显示目前已存在的所有执行记录。
  • -p:重新执行某条执行记录,配合-i使用,如tt -i 1001 -p
  • -s:通过OGNL表达式进行查找。
  • -M:指定接收结果的字节上限,默认为1KB
  • ---replay-times:配合-p使用,指定重新执行N次。
  • --replay-interval:执行多次时,每次执行时的间隔时间。
  • 重新执行3次某记录,每次间隔500mstt -i 1001 -p --replay-times 3 --replay-interval 500
4.3 monitor命令

monitor:对指定的方法执行进行监控,使用参考,请参见 官方文档

monitor | arthas (aliyun.com)

monitor 对匹配 class-patternmethod-patterncondition-express的类、方法的调用进行监控。

monitor 命令是一个非实时返回命令.

实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。

服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 命令不会引起原有业务逻辑的改变。

可选项如下:

  • -c:指定监控的周期,默认为60s
  • -n:指定监控的周期次数。
  • 使用示例:monitor -c 10 -n 3 demo.MathGame primeFactors
4.4 stack命令

stack:输出当前方法被调用的调用路径。

很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

使用参考,请参见 官方文档

stack | arthas (aliyun.com)

4.5 trace命令

trace 命令能主动搜索 class-patternmethod-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

使用参考,请参见 官方文档

trace | arthas (aliyun.com)

trace:方法内部调用路径,并输出方法路径上的每个节点上耗时,可选项如下:

  • -i:跳过JVM的本地方法。
  • -n:和之前的-n同义。

5、OGNL表达式

rthas中的很多进阶操作都需要依赖于OGNL表达式进行编写,在线上排查时,OGNL表达式往往会结合tt、watch、monitor、stack、trace等多个命令共同使用。

arthas执行ognl表达式,获取对应的jvm对象数据。

基本语法
ognl express -c {hashCode} --classLoaderClass {当前的全路径 ClassLoader 信息} -x {number}
参数说明
参数名称参数说明
express执行的表达式
[c:]执行表达式的 ClassLoader 的 hashcode,默认值是 SystemClassLoader
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[x]结果对象的展开层次,默认值 1
①、调用静态属性

ognl ‘@类的全限定名@静态属性名’

示例:

[arthas@80573]$ ognl '@demo.MathGame@random'
②、调用静态方法

ognl ‘@类的全限定名@静态方法名(“参数”)’

示例1:调用入参为基本数据类型和集合的方法:

[arthas@80573]$ ognl '@demo.MathGame@print(100,{1,2,3,4})' -x 1
null
③、调用构造方法

ognl ‘new 类的全限定名()’

示例1:调用无参创建对象

[arthas@80573]$ ognl 'new java.lang.Object()'

示例2:调用有参创建对象

[arthas@80573]$ ognl 'new xxx.xx.xxx("xx",x,{1,2,3})'
④、读取不同类型的值

示例1:读取引用对象类型的属性值

[arthas@80573]$ ognl '@类全限定名@方法名("参数").属性名称'

示例2:读取List类型的指定元素

[arthas@80573]$ ognl '@类全限定名@方法名("参数")[下标]'

详细的OGNL语法可参考:官方指南

Arthas线上常用场景

Arthas中集成了大部分JDK工具的功能实现,

在线上情况时,主要使用场景:

  • CPU占用过高
  • 线程阻塞
  • 死锁
  • 代码动态修改
  • 方法执行缓慢
  • 排查404
  • …等。

1、排查CPU占用过高问题

  • thread -n 10命令查看CPU占用资源最高的10条线程。
  • thread命令查看线程的执行信息,定位到具体的方法。
  • monitor命令对目标方法进行监控,查看方法的调用次数与耗时。
  • ④分析monitor命令查询出的结果,定位问题根源,确定是由于调用过于频繁导致的,还是内部代码逻辑问题。
  • ⑤使用jad命令反编译class文件,根据前面分析的原因排查代码并改善。

2、排查线程阻塞问题

  • thread筛选所有阻塞状态的线程。
  • ②根据线程名称定位具体的业务模块,再选中该业务中的一条线程查看堆栈信息。
  • ③根据线程堆栈信息定位导致阻塞的具体方法,再利用stack命令查看方法堆栈信息。
  • ④利用jad工具反编译源码,分析业务逻辑代码并改善。

3、排查死锁问题

  • ①利用Arthas来检测死锁特别简单,只需要执行一行命令thread -b即可。

4、排查方法执行过慢问题

  • ①通过trace命令排查方法执行速度,trace xx类 xx方法 '#cost>50ms',观测执行时间大于50ms的该方法的调用信息。
  • ②可以结合正则表达式,同时排查多个类、多个方法,trace -E ClassA|ClassB method1|method2|method3

5、动态修改线上代码

上线之后,发现代码有一处小地方存在逻辑错误需要更改,可以直接线上修改,而不用重启。

  • ①通过jad将要修改的类反编译为.java文件,输出到指定目录。
  • ②本地纠正.java文件后,通过mc命令重新编译.java文件。
  • ③通过retransform命令将刚编译的.class文件再次加载到JVM中。

总之,Arthas通过与目标应用程序交互的命令行界面,用户可以方便地执行各种诊断和分析任务,以解决性能问题、内存泄漏等。

这种实现原理使Arthas成为一个强大的Java诊断工具。

Arthas底层原理

Arthas是一个用于诊断和分析Java应用程序的开源工具,它的实现原理主要包括以下几个关键方面:

  • Java Agent

    Arthas作为一个Java诊断工具,通过Java Agent技术来实现对目标Java应用程序的监控和诊断。Java Agent允许Arthas以字节码级别修改和增强目标应用程序的类,从而使其具备监控和诊断的能力。

  • Instrumentation API

    Arthas使用Java的Instrumentation API来实现Java Agent。Instrumentation API允许Arthas在目标应用程序加载类的过程中插入字节码增强代码,以收集信息、执行命令或修改应用程序的行为。

  • 字节码增强

    Arthas通过字节码增强技术来修改目标应用程序的类。它可以在类加载期间动态地修改类的字节码,以添加监控、日志记录或诊断代码。这使得Arthas能够捕获应用程序的运行时信息,并执行一系列诊断和分析任务。

  • 命令行工具

    提供了一个交互式的命令行界面,用户可以在命令行中输入各种命令来与目标应用程序进行交互。这些命令可以查询应用程序的状态、分析性能问题、诊断内存泄漏等。

  • 类加载和类转换

    Arthas在应用程序启动时作为Java Agent被加载,然后通过Instrumentation API监视目标应用程序的类加载过程。一旦目标应用程序加载了新的类,Arthas可以拦截类加载事件并对类进行必要的字节码增强。

  • 类加载器隔离

    Arthas采用了一种类加载器隔离的机制,以确保Arthas的Agent类加载器与目标应用程序的类加载器相互隔离,防止冲突和干扰。

总之,Arthas的实现原理基于Java Agent、Instrumentation API和字节码增强技术,使其能够动态地监控、诊断和分析运行中的Java应用程序。

基于Java Agent、Instrumentation的产品,除了arthas,常用的还有pinpoint、skywalking这些非常有名气 的产品。

关于Java Agent/Instrumentation 的知识,请参见尼恩的博客:

ByteBuddy(史上最全)

关于skywalking 的架构和源码,请参见尼恩的视频:

《第24章视频:资深架构必备,彻底穿透Skywalking链路跟踪源码、JavaAgent探针技术》

参考文献:

简介 | arthas (aliyun.com)

说在最后: “offer自由” 很容易的

Java Agent、Instrumentation、arthas 相关的面试题,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。

尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。

狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” !

尼恩技术圣经系列PDF

……完整版尼恩技术圣经PDF集群,请找尼恩领取

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

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