-
下载安装
- 下载arthas的jar包
- 将jar包下载后放到指定的文件夹中
-
启动与结束
- win+r打开命令行
- 使用命令运行arthas对应的jar包
- 会列出所有的java服务
- 选择要监控的服务,输入服务列表索引数字后回车,就能正常启动了
- 如果回车后报端口已经被使用,说明之前arthas服务绑定监控的服务后没有正常结束就退出了,绑定关系依然存在。
- 启动之后如果需要结束arthas服务则使用命令quit或者stop
- 启动之后可以直接访问Arthas Console
-
基本命令的使用
-
dashboard
- 全局监控
- 可以概览程序的 线程、内存、GC、运行环境信息。
-
thread
- 线程监控
- 使用 thread查看所有线程信息,同时会列出每个线程的 CPU 使用率。
- 可以用来检查哪些线程占用CPU较高
- 使用“thread 【线程id】”来查看该线程的信息
- 使用“thread -n [显示的线程个数]”来查看thread -n [显示的线程个数]
- 使用“thread | grep pool”查看线程池里线程信息。
- 使用“thread -b”如果服务中存在死锁,则可以直接定位到死锁的位置
-
jad
- 可以使用jad将对应的代码进行发编译查看环境中的代码
- jad 【全限定类名】
-
sc
- 使用 **sc -d -f ** 命令查看类的字段信息。
-
sm
- 使用 sm 命令查看类的方法信息。
-
ognl
- 在Arthas中可以通过ognl表达式在java程序的运行阶段获取java类的静态方法、调用静态方法、new出对象操作成员属性和方法等操作。
- 这些能力可以用于排查线上一些奇奇怪怪的问题,比如感觉线上某个静态属性值不对,可以通过ognl表达式获取对应的静态属性值查看,又或者在Spring项目中注入对象都是单例的,可以通过getBean(name)的方式获取到具体的单例对象,然后对这个单例对象进行操作,同时也能调用对象中的一些方法,在不提供http接口的情况下实现某些特定的线上测试。
- 例子
- 获取静态属性ognl表达式
- 调用静态方法
- 使用ognl表达式操作对象的非静态属性和非静态方法
-
public class OgnlDemo02 { public static String s1="s1-public-static-v"; public String s2="s2-public-v"; private String s3="s3-private-v"; public static OgnlDemo02 ognlDemo02=new OgnlDemo02(); public String getS1() { return OgnlDemo02.s1; } public void setS1(String s1) { OgnlDemo02.s1 = s1; } public String getS2() { return s2; } public void setS2(String s2) { this.s2 = s2; } public String getS3() { return s3; } public void setS3(String s3) { this.s3 = s3; } }
-
- 获取非静态属性ognl表达式
- 通过静态属性ognlDemo02对象直接获取公有非静态属性值(这种方法没法获取静态属性)
- ognl '@[email protected]'
- 通过静态属性ognlDemo02对象直接获取私有非静态属性值(这种方法没法获取静态属性)
- ognl '@[email protected]'
- 通过静态属性ognlDemo02对象的成员方法获取对应属性值
- ognl '@[email protected]()'
- 通过静态属性ognlDemo02对象直接获取公有非静态属性值(这种方法没法获取静态属性)
- 调用非静态方法ognl表达式
- 通过静态属性ognlDemo02对象调用非静态方法
- ognl '@[email protected]()'
- 通过静态属性ognlDemo02对象调用非静态方法
- 获取非静态属性ognl表达式
- 使用ognl表达式创建一个对象,并对这个对象进行操作
-
public class User { private Long uid; private String nickName; public User(Long uid, String nickName) { this.uid = uid; this.nickName = nickName; } public Long getUid() { return uid; } public void setUid(Long uid) { this.uid = uid; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } }
-
# 创建一个User对象什么也不做 [arthas@27880]$ ognl 'new com.kerwin.arthas.demo.User(100,"kerwin")' @User[ uid=@Long[100], nickName=@String[kerwin], ] # 创建一个User对象赋值给user,后续可以在别的地方对创建出来的对象进行操作 [arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(100,"kerwin")' @User[ uid=@Long[100], nickName=@String[kerwin], ]
-
[arthas@27880]$ ognl 'new com.kerwin.arthas.demo.User(10001,"kerwin").getNickName()' @String[kerwin] ] [arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin").getNickName()' @String[kerwin] ] [arthas@27880]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),#user.getNickName()' @String[kerwin] ]
-
public class OgnlDemo03 { public static OgnlDemo03 ognlDemo03 = new OgnlDemo03(); private User user; private static User staticUser; private static List<String> lists; private static Map<String,String> maps; public User setUser(User user){ this.user = user; return user; } public static User setStaticUser(User user){ OgnlDemo03.staticUser = user; return user; } public static User getMyUser(){ return new User(10002L,"kerwin2"); } public static User changeUser(User user){ return new User(user.getUid(),user.getNickName()+"---changeUser"); } public static List<String> setLists(List<String> lists){ OgnlDemo03.lists = lists; return lists; } public static Map<String,String> setMaps(Map<String,String> maps){ OgnlDemo03.maps = maps; return maps; } }
-
[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),@com.kerwin.arthas.demo.OgnlDemo03@setStaticUser(#user)' @User[ uid=@Long[10001], nickName=@String[kerwin], ]
-
[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),@[email protected](#user)' @User[ uid=@Long[10001], nickName=@String[kerwin], ]
-
[arthas@24600]$ ognl '#user = new com.kerwin.arthas.demo.User(10001,"kerwin"),new com.kerwin.arthas.demo.OgnlDemo03().setUser(#user)' @User[ uid=@Long[10001], nickName=@String[kerwin], ]
-
# {#user1,#user2} 代表将user1、user2这两个对象作为数组输出在控制台,因为这里没有加-x 2默认展开层级为1所以输出的是对象内存地址 [arthas@18904]$ ognl '#user1 = @com.kerwin.arthas.demo.OgnlDemo03@getMyUser(),#user2 = @com.kerwin.arthas.demo.OgnlDemo03@changeUser(#user1),{#user1,#user2}' @ArrayList[ @User[com.kerwin.arthas.demo.User@30833e75], @User[com.kerwin.arthas.demo.User@70ca5419], ] # 加上-x 2 可以展开数组内部对象 [arthas@18904]$ ognl '#user1 = @com.kerwin.arthas.demo.OgnlDemo03@getMyUser(),#user2 = @com.kerwin.arthas.demo.OgnlDemo03@changeUser(#user1),{#user1,#user2}' -x 2 @ArrayList[ @User[ uid=@Long[10002], nickName=@String[kerwin2], ], @User[ uid=@Long[10002], nickName=@String[kerwin2---changeUser], ], ]
-
[arthas@18904]$ ognl '@com.kerwin.arthas.demo.OgnlDemo03@setLists({"k1","k2","k3"})' @ArrayList[ @String[k1], @String[k2], @String[k3], ]
-
[arthas@18904]$ ognl '#map = #{"id":10003L,"nickName":"k3"},@com.kerwin.arthas.demo.OgnlDemo03@setMaps(#map)' @LinkedHashMap[ @String[id]:@Long[10003], @String[nickName]:@String[k3], ]
- 实践操作:使用ognl表达式获取Spring上下文中对象,并进行操作
-
@Service public class OgnlDemoService { private String description; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
-
@Component public class SpringApplicationContext implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringApplicationContext.applicationContext = applicationContext; } /** * 通过class获取Bean */ public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } /** * 通过name获取 Bean. */ public static Object getBean(String name) { return applicationContext.getBean(name); } }
-
# 通过beanName获取 [arthas@30000]$ ognl '@com.example.test_mybatis.controller.SpringApplicationContext@getBean("ognlDemoService")' @OgnlDemoService[ description=null, ] # 通过class获取 [arthas@30000]$ ognl '#OgnlDemoServiceClass [email protected]_mybatis.service.OgnlDemoService@class,@com.kerwin.arthas.utils.Sp ringApplicationContext@getBean(#OgnlDemoServiceClass)' @OgnlDemoService[ description=null, ]
-
[arthas@30000]$ ognl '@com.example.test_mybatis.controller.SpringApplicationContext@getBean("ognlDemoService").description' null [arthas@30000]$ ognl '@com.example.test_mybatis.controller.SpringApplicationContext@getBean("ognlDemoService").setDescription("HelloW orld")' null [arthas@30000]$ ognl '@com.example.test_mybatis.controller.SpringApplicationContext@getBean("ognlDemoService").getDescription()' @String[Hello World]
-
trace
- 使用 trace 命令可以跟踪统计方法耗时,经常用于排查运行较慢、耗时较长的场景
- 测试代码
-
package com.example.test_mybatis.controller; import com.example.test_mybatis.service.UserServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; 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 { // 模拟用户查询 userService.get(uid); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("uid", uid); hashMap.put("name", "name" + uid); return hashMap; } } package com.example.test_mybatis.service; 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; } }
- 在arthas中使用命令 trace com.example.test_mybatis.controller.UserController getUser
- 很清楚的看到是 com.UserServiceImpl的 mysql方法耗时是最高的。
-
monitor
- 使用 monitor 命令监控统计方法的执行情况。
-
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()}'
- 可以对传入的参数进行操作
-
stack
- 使用stack查看方法的调用情况
-
tt
- tt 命令方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 。
- 使用 tt 命令记录方法执行的详细情况
- 开始记录方法调用信息:tt -t com.UserServiceImpl check
- 查看记录的方法调用信息: tt -l
- 查看调用记录的详细信息(-i 指定 INDEX): tt -i 1001
- 重新发起调用,使用指定记录,使用 -p 重新调用。