学习arthas(四)系统命令

Arthas
placeholder image
admin 发布于:2023-09-24 18:48:38
阅读:loading

系统的命令并不是说操作系统的命令,这里是指与监控的Java进程相应的服务交互的指令,包含调用起来比较简单,可以查看整体进程信息的,所以我觉得这部分的命令范围如下文所示。

1.dashboard

描述:概述目标jvm的线程,内存,gc, vm, tomcat信息。

当前系统的实时数据面板,按 ctrl+c 退出。默认每间隔 5 秒执行一次实时数据的输出。

该命令有两个参数:

-i, --interval <value> 这个命令执行的间隔时间,单位毫秒,默认5000毫秒,即5秒钟;

-n, --number-of-execution <value> 这个命令将被执行的次数;

如:每间隔1秒输出一次,共执行2次,命令:dashboard -i 1000 -n 2

image.png

(帮助文档)

image.png

(运行结果)

2.thread

描述:查看当前线程信息,查看线程的堆栈。

参数名称

 参数说明

 id

 线程 id

 [n:]

 指定最忙的前 N 个线程并打印堆栈

 [b]

 找出当前阻塞其他线程的线程

 [i <value>]

 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200

 [--all]

 显示所有匹配的线程

(1)thread 当没有参数时,显示第一页线程的信息,默认按照 CPU 增量时间降序排列,只显示第一页数据,避免滚屏。

Threads Total: 21, NEW: 0, RUNNABLE: 9, BLOCKED: 0, WAITING: 4, TIMED_WAITING: 3, TERMINATED: 0, Internal threads: 5

(2)thread --all, 显示所有匹配的线程信息,有时需要获取全部 JVM 的线程数据进行分析。

(3)thread id,显示指定线程的运行堆栈,如thread 10 将输出线程栈。

(4)thread -b, 找出当前阻塞其他线程的线程

有时候我们发现应用卡住了,通常是由于某个线程拿住了某个锁,并且其他线程都在等待这把锁造成的。为了排查这类问题,arthas 提供了thread -b ,一键找出那个罪魁祸首。

注意,目前只支持找出 synchronized 关键字阻塞住的线程,如果是java.util.concurrent.Lock ,目前还不支持。

(5)thread -i, 指定采样时间间隔。

thread -i 1000 : 统计最近 1000ms 内的线程 CPU 时间。

thread -n 3 -i 1000 : 列出 1000ms 内最忙的 3 个线程栈。

(6)thread -state,查看指定状态的线程。

thread -state WAITING 查看等待状态的线程。

(7)thread 命令可正常正常接一些管道命令,如:thread --all | grep main。

image.png

(帮助文档)

DELTA_TIME

指的是线程等待同步锁的时间或线程阻塞其他线程的时间的增量值。DELTA_TIME 的单位为毫秒。

在输出的信息中,如果 DELTA_TIME 的值大于 0,则表明该线程在等待同步锁或者阻塞其他线程时,它的等待/阻塞时间有所增长。如果 DELTA_TIME 的值为 0,则表明该线程在两次采样周期内,它的等待/阻塞时间没有增长。

DELTA_TIME 通常会与 WAITED_TIME/BLOCKED_TIME 配合使用,它们的差异有助于判断线程的行为是否正常和排查性能问题。

需要注意的是,DELTA_TIME 只是一个增量值,如果需要查看线程等待/阻塞的总时间,通过 WAITED_TIME/BLOCKED_TIME 查看更为准确。

TIME

TIME 指的是线程等待同步锁的时间或线程阻塞其他线程的时间的总时间。TIME 的单位为毫秒。

在输出的信息中,TIME 表示该线程在等待同步锁或者阻塞其他线程的总时间。相比而言,DELTA_TIME 只是一个增量值,它的值是当前采样周期中等待/阻塞时间增量,无法反映总等待/阻塞时间的变化。TIME 的值可以帮助我们获取更准确的线程等待/阻塞时间信息。

TIME 通常会与 WAITED_COUNT/BLOCKED_COUNT/STACK_TRACE 配合使用,来判断线程的行为是否正常,并帮助进行线程的调优。

3.jvm

描述:jvm 命令可以查看当前 JVM 信息,如 JVM版本、厂商、启动时间、PATH、CLASSPATH、加载类个数、内存、操作系统、线程等参数,其中线程参数可以有:JVM 当前活跃的线程数、JVM 当前活跃的守护线程数、从 JVM 启动开始曾经活着的最大线程数、从 JVM 启动开始总共启动过的线程次数、 JVM 当前死锁的线程数 。命令比较简单,无过多的参数,参考命令如下:

image.png

(帮助文档)

4.sysprop

描述:查看当前 JVM 的系统属性(System Property),程序运行时JVM默认加载的有一系列的系统属性,受System.setProperties代码的设置和运行Java脚本-D设置的参数,可以得到一些自定义的系统参数(Jrebel在启动时的命令通常也会增加系统变量)。

该命令可以罗列所有的系统属性参数,也可以获取某个属性的值和设置某个属性的值。

image.png

(帮助文档)

除了获取系统内置的属性参数外,同样也支持使用代码和启动命令中设置的参数值,参考如下代码所示:

/**
 * 演示系统自定义属性获取
 *
 * @author chendd
 * @date 2023/9/24 19:54
 */
public class SysProperty {

    public static void main(String[] args) throws Exception {
        System.setProperty("chendd.name" , "chendongdong");
        for (int i = 0; i < 100; i++) {
            System.out.println(System.getProperty("chendd.name" , "chendd.name 未获取到"));
            System.out.println(System.getProperty("chendd.blog" , "chendd.blog 未获取到"));
            TimeUnit.SECONDS.sleep(1L);
        }

    }

}

(1)若直接使用“java -Dchendd.blog=www.chendd.cn cn.chendd.arthas.exmples.sysprop.SysProperty”命令运行的Java程序可以获取到以上两个参数的值;

(2)若直接使用“java -Dchendd.name=chendongdong -jar chendd-arthas.jar”命令运行的Java程序也可以获取到以上两个参数的值;

(3)小知识:在运行Java应用程序时,除了给main方法的args[]设置参数值,也可以通过此种方式给程序赋值;

5.sysenv

描述:查看当前 JVM 的环境属性(System Environment Variables),与sysprop 类似,可以查看全量的JVM环境属性,也可以按名称单个查看。

image.png

(帮助文档)

可以用Java代码通过设置系统环境变量的方式实现动态加载某些动态链接库的方式来验证是否影响到的相关的环境信息,尝试使用System.load和System.setProperty("java.library.path")两种方式并未生效。

6.vmoption

描述:查看,更新 VM 诊断相关的参数。

image.png

7.vmtool

描述:vmtool 利用JVMTI接口,实现查询内存对象,强制GC、获取实例、中断线程等功能。

JVM TM 工具接口 (JVM  TI) 是开发和监视工具使用的编程接口。它提供了一种检查状态并控制在 Java TM 虚拟机 (VM) 中运行的应用程序执行的方法。

JVM  TI 旨在为需要访问 VM 状态的各种工具提供 VM 接口,包括但不限于:分析、调试、监视、线程分析和覆盖率分析工具。

JVM TI 可能并非在 Java TM 虚拟机的所有实现中都可用 。

JVM  TI 是一个双向接口。JVM TI 的客户端 (以下称为代理)可以通过事件获知有趣的发生情况。JVM TI 可以通过许多函数 来查询和控制应用程序 ,无论是响应事件还是独立于事件。

代理与执行正在检查的应用程序的虚拟机在同一进程中运行并直接通信。这种通信是通过本机接口 (JVM  TI) 进行的。本机进程内接口允许最大程度的控制,同时对工具的干扰最小。通常,代理相对紧凑。它们可以由一个单独的进程控制,该进程实现工具的大部分功能,而不会干扰目标应用程序的正常执行。

一、查找 jvm 里的字符串对象,参考命令:

vmtool --action getInstances --className java.lang.String

vmtool --action getInstances --className java.lang.String --limit 10

(1)通过 --limit 参数,可以限制返回值数量,避免获取超大数据时对 JVM 造成压力。默认值是 10。

(2)如果设置--limit 为负数,则遍历所有对象。

二、查找 spring context

vmtool --action getInstances --className org.springframework.context.ApplicationContext

指定返回结果展开层数

getInstances action 返回结果绑定到instances 变量上,它是数组。

通过 -x /--expand 参数可以指定结果的展开层次,默认值是 1。

vmtool --action getInstances --className org.springframework.context.ApplicationContext -x 2

执行表达式

getInstances action 返回结果绑定到instances 变量上,它是数组。可以通过--express 参数执行指定的表达式。

查找所有的 spring beans 名字:

vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBeanDefinitionNames()'

调用userController.findUserById(1) 函数:

vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBean("userController").findUserById(1)'

直接获取 UserController 并调用方法

vmtool --action getInstances --className com.example.demo.arthas.user.UserController --express 'instances[0].findUserById(1)'

三、指定 classloader

可以通过sc 命令查找到加载 class 的 classloader。

sc -d org.springframework.context.ApplicationContext

通过上面命令得到 org.springframework.boot.loader.LaunchedURLClassLoader hashcode 之后用-c / --classloader 参数指定,这里使用 --classLoaderClass 来指定 classloader

vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.springframework.context.ApplicationContext

/**
 * 演示vmtool获取
 * @author chendd
 * @date 2023/9/24 21:03
 */
public class VmTool {

    private static User u1 = new User("chendd-1");
    private static User u2 = new User("chendd-2");
    private User u3 = new User("chendd-3");

    private String name = "chendongdong";
    private String website = "http://www.chendd.cn";

    public static void main(String[] args) throws Exception {
        String name1 = new String("cdd-1");
        String name2 = "cdd-2";
        User u4 = new User("chendd-3");
        new Thread(() -> new User("cdd-5")).start();
        System.in.read();
    }

    public static class User {

        private String name;

        public User(String name) {
            this.name = name;
        }

        //getter setter
    }
}
[arthas@8500]$ vmtool --action getInstances --className  cn.chendd.arthas.exmples.vmtool.VmTool
@VmTool[][isEmpty=true;size=0]
[arthas@8500]$ 
[arthas@8500]$ vmtool --action getInstances --className  cn.chendd.arthas.exmples.vmtool.VmTool$User -x 2
@User[][
    @User[
        name=@String[chendd-3],
    ],
    @User[
        name=@String[chendd-1],
    ],
    @User[
        name=@String[chendd-2],
    ],
]
[arthas@8500]$ vmtool --action getInstances --className java.lang.String
@String[][
    @String[vmtool --action getInstances --className java.lang.String],
    @String[todo],
    @String[Todo ],
    @String[Offset cannot bebe greater than the buffer size],
    @String[vmtool],
    @String[vmtool],
    @String[ ],
    @String[--action],
    @String[--action],
    @String[ ],
]
[arthas@8500]$ vmtool --action getInstances --className  cn.chendd.arthas.exmples.vmtool.VmTool$User --express '"chendd-1".equals(instances[0].getName())' -x 2
@Boolean[false]
[arthas@8500]$ vmtool --action getInstances --className  cn.chendd.arthas.exmples.vmtool.VmTool$User --express '"chendd-1".equals(instances[1].g@Boolean[true]2
[arthas@8500]$ 
[arthas@8500]$ vmtool --action getInstances --className  cn.chendd.arthas.exmples.vmtool.VmTool$User --express '"chendd-1".equals(instances[2].g@Boolean[false]

示例说明

(1)上述代码定义了静态示例和对象示例,同时在方法内部也定义了实例变量;

(2)上述代码演示了获取类的示例对象,内部类的示例对象,结果输出的参数值得层级,结果输出的表达式执行结果;

(3)示例输出结果仅包含有3个全局的实例对象;

(4)以上代码仅返回了3个实例对象,若下标为3时抛出数组下标越界ArrayIndexOutOfBoundsException;

8.perfcounter

描述:查看当前 JVM 的 Perf Counter 信息,显示性能计数器信息。

可以用-d 参数打印更多信息:

perfcounter -d

备注:对于 jdk9 以上的应用

如果没有打印出信息,应用在启动时,加下面的参数:

--add-opens java.base/jdk.internal.perf=ALL-UNNAMED --add-exports java.base/jdk.internal.perf=ALL-UNNAMED --add-opens java.management/sun.management.counter.perf=ALL-UNNAMED --add-opens java.management/sun.management.counter=ALL-UNNAMED

没有太多的介绍,基本罗列了非常多的`没有用`的参数信息。

9.logger

/**
 * 演示日志文件
 * @author chendd
 * @date 2023/9/24 21:50
 */
public class Logback {

    public static void main(String[] args) throws Exception {
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(lc);
        lc.reset(); // 清除现有的默认配置
        // 指定配置文件的路径
        try {
            configurator.doConfigure(Logback.class.getResource("logback.xml"));
        } catch (JoranException e) {
            // 如果配置文件加载失败,则输出错误信息
            e.printStackTrace();
        }
        // 确认配置是否加载成功
        StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
        // 使用 logger 输出日志
        for (int i = 0; i < 1000; i++) {
            AdminLogger.getLogger().debug("AdminLogger debug i = {}" , i);
            AdminLogger.getLogger().info("AdminLogger info i = {}" , i);
            AdminLogger.getLogger().warn("AdminLogger warn i = {}" , i);
            WebLogger.getLogger().debug("WebLogger debug i = {}" , i);
            WebLogger.getLogger().info("WebLogger info i = {}" , i);
            WebLogger.getLogger().warn("WebLogger warn i = {}" , i);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

image.png

10.getstatic

(1)通过 getstatic 命令可以方便的查看类的静态属性。使用方法为`getstatic class_name field_name`,推荐直接使用ognl命令,更加灵活。

(2)如果该静态属性是一个复杂对象,还可以支持在该属性上通过 ognl 表示进行遍历,过滤,访问对象的内部属性等操作。

(3)获取的静态属性不限制访问修饰符,对于private私有的也可以正常访问。

(4)花了几分钟测试了一下,让GetStatic继承了一下Thread类,但是无法拿到父类中的静态字段,提示字段不存在。

/**
 * 演示获取静态字段
 *
 * @author chendd
 * @date 2023/9/25 13:16
 */
public class GetStatic {

    private static String USER_NAME = "chendongdong";

    private static final String WEBSITE = "https://www.chendd.cn";

    private static Point point = new Point(9 , 11);

    public static void main(String[] args) throws IOException {

        System.in.read();
    }
    
}

image.png

11.ognl

描述:执行 ognl 表达式:调用静态函数、获取静态类的静态字段、通过 hashcode 指定 ClassLoader、执行多行表达式,赋值给临时变量,返回一个 List。

有一个单独的 ognl 命令,可以动态执行代码。

(1)调用 static 函数:

ognl '@java.lang.System@out.println("hello ognl")'

可以检查Tab 1 (不是 arthas 的 Tab 2)里的应用进程的输出,可以发现打印出了hello ognl 。

(2)查找 UserController 的 ClassLoader:

sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash

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

如果你使用-c ,你需要手动输入 hashcode:-c <hashcode>

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

--classLoaderClass 的值是 ClassLoader 的类名,只有匹配到唯一的 ClassLoader 实例时才能工作,目的是方便输入通用命令,而-c <hashcode> 是动态变化的。

(3)获取静态类的静态字段

获取UserController 类里的logger 字段:

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @com.example.demo.arthas.user.UserController@logger

还可以通过-x 参数控制返回值的展开层数。比如:

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -x 2 @com.example.demo.arthas.user.UserController@logger

(4)执行多行表达式,赋值给临时变量,返回一个 List

ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'

(5)更多

在 Arthas 里ognl 表达式是很重要的功能,在很多命令里都可以使用ognl 表达式。

一些更复杂的用法,可以参考:

OGNL 特殊用法请参考:https://github.com/alibaba/arthas/issues/71

OGNL 表达式官方指南:https://commons.apache.org/proper/commons-ognl/language-guide.html

(6)OGNL 一般语法:

A.索引

:数组和列表的索引、JavaBeans 索引属性、变量引用、方法调用;

B.复杂链式表达式:等价于;

C.集合操作:新建列表、新建原生数组、新建 Maps、集合的投影、查找集合元素、集合的虚拟属性;

D.构造函数、静态方法、静态属性、伪 lambda 表达式;

E.补充:更多详细语法请查看官方文档,https://commons.apache.org/proper/commons-ognl/language-guide.html

/**
 * 演示Ognl
 *
 * @author chendd
 * @date 2023/9/25 20:06
 */
public class Ognl {

    public static void main(String[] args) throws IOException {
        System.out.println("按任意键回车后退出...");
        System.in.read();
    }

    public String getJavaVersion() {
        return System.getProperty("java.version");
    }

    public String sayHello(String name) {
        return "hello , " + name;
    }

    public static String create(Point point , String value) {
        return "x = " + point.x + ", y = " + point.y + " , value = " + value;
    }

    public static void stop() {
        System.exit(0);
    }

}

image.png

12.mbean

描述:查看 Mbean 的信息,这个命令可以便捷的查看或监控 Mbean 的属性信息。

(1)使用Spring Boot构建一个MBean的程序;

(2)使用Java自带的jconsole软件验证MBean是否有效,可参见本站博文《Spring Boot 中的JMX使用》;

(3)查看Mbean列表和Mbean的元素据如下:

/**
 * 演示MBean
 * @author chendd
 * @date 2023/9/25 20:55
 */
@Component
@ManagedResource(objectName = "cn.chendd.arthas:name=MBeanDemo" , description = "示例演示")
public class MBeanDemo {

    @ManagedAttribute(description = "获取系统名称")
    public String getName() {
        return "chendd";
    }

    @ManagedOperation(description = "输出Hello")
    @ManagedOperationParameters(value = {
            @ManagedOperationParameter(name = "name", description = "名称")
    })
    public String sayHello(String name) {
        return String.format("hello , %s" , name);
    }

    @ManagedOperation(description = "关闭服务器")
    public void shutdown() {
        System.exit(0);
    }
}

image.png

13.heapdump

描述:dump java heap, 类似 jmap 命令的 heap dump 功能。

生成文件在arthas-output目录,可以通过浏览器下载: http://localhost:8563/arthas-output/

(1)dump 到指定文件:heapdump arthas-output/dump.hprof

(2)只 dump live 对象:heapdump --live /tmp/dump.hprof

(3)dump 到临时文件:heapdump

image.png

14.更多下载

(1)由于篇幅已经太多,专门上传了一些图片,有兴趣可以下载查看;

(2)点击下载:《arthas-系统命令.zip

(3)下载附件预览如下图所示:

image.png

 点赞


 发表评论

当前回复:作者

 评论列表


留言区