基于BTrace监控调试Java代码
BTrace是Java的一个动态代码追踪工具,通过编写btrace脚本,它可以动态的向目标应用程序的字节码注入追踪代码,通过修改字节码的方式,达到监控调试和定位问题的目的,是解决线上问题的利器。
BTrace项目的Github主页 https://github.com/btraceio/btrace,本文中示例代码在 https://github.com/cellei/btrace-practice
快速开始
我们使用 BTrace User's Guide 中的示例来做一个快速开始:
import com.sun.btrace.annotations.*;
@BTrace
public class HelloWorld {
@OnMethod(
clazz="java.lang.Thread",
method="start",
location = @Location(Kind.ENTRY)
)
public static void func() {
BTraceUtils.println("about to start a thread!");
}
}
BTrace包下载解压后,把bin目录加入环境变量,执行命令btrace <pid> HelloWorld.java
,pid是Java目标程序的进程号,可以使用jps
或者ps
命令查询到。这样就执行了BTrace脚本程序,每启动一个线程,就会打印出 "about to start a thread!",BTrace脚本已经正常执行了,不需要重启目标Java程序。
若想要退出BTrace的话,按下ctrl+c
,再选择1
就可以了,也可以在脚本中使用代码BTraceUtils.Sys.exit(0)
来退出。
简单分析一下这段代码,@BTrace注解表明这是一段BTrace脚本,@OnMethod注解以及func方法的参数列表(重载时),指定了要跟踪的目标代码,称为探测点(Probe Point),func方法体内的代码就是跟踪行为(Trace Action),即跟踪到目标代码后所执行的代码。
btrace 命令格式:btrace <pid> <btrace-script>
,会直接打印结果到命令行下,也可使用转向命令输出结果到文件,例如btrace <pid> HelloWorld.java > btrace.log
如果执行的BTrace脚本是".java"源文件,btrace
命令执行时会首先编译为字节码,建议预编译为".class"字节码文件后,再执行编译后的BTrace脚本。
写BTrace脚本并且预编译时,需要引入BTrace的jar包,以Maven本地jar包形式引入如下:
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-client</artifactId>
<version>1.3.11.2</version>
<type>jar</type>
<scope>system</scope>
<systemPath>${basedir}/src/main/resources/lib/btrace-client.jar</systemPath>
</dependency>
使用限制
由于BTrace脚本会向线上代码中注入字节码,即使退出后,注入的字节码也不会恢复,注入的监控代码依然会执行,所以使用BTrace时有一些限制必须遵守,正因为这些限制,BTrace才能更放心的在线上调试中使用。
- BTrace脚本中不能创建对象、创建数组、捕获和抛出异常。
- 不能调用实例或静态方法,只能调用
com.sun.btrace.BTraceUtils
中的静态方法。 - 不能把目标程序的类或对象赋值给静态或实例字段。
- 不能定义外部, 内部, 匿名, 本地类。
- 不能使用同步
synchronized
代码块和同步方法。 - 不能使用
for
、while
等循环语句。 - 不能扩展类,父类必须是Object,不允许实现接口。
- 不能使用
assert
语句,不能使用Class
字面值,例如Class<String> c = String.class;
BTrace注解
注解都在com.sun.btrace.annotations
包下,分为类注解、方法注解、方法参数注解、属性注解。
类注解
- @BTrace:用来指明这个Java类是个BTrace脚本程序,被BTrace编译器和BTrace代理执行。
- @DTrace及@DTraceRef:是单独给Solaris系统使用的,一般使用不到。
方法注解
- @OnMethod:用来指定监控的目标代码,它有三个常用属性,
clazz
、method
、location
。
- clazz 属性用来指定目标代码的类名,支持全限定名/正则/父类/注解形式。全限定名,例如
java.lang.Thread
;也可以使用正则表达式匹配,例如/java\\.lang\\..+/
;父类或接口的形式,例如+java.lang.Runnable
,注解形式,例如@javax.annotation.Resource
。内部类使用$符号连接。 - method 属性用来指定目标代码的方法名,支持全限定名/正则/注解形式,特殊的,指定构造方法时使用
<init>
表示,目标类有重载的方法时,根据@OnMethod修饰的方法的参数列表具体指明哪个目标方法。 - location 属性指定目标代码的拦截时机,比如是进入目标方法时拦截(默认),还是目标方法返回值的时候拦截,后面会详细讲到。
- @OnTimer:用来周期性的、每隔N毫秒定期执行该注解修饰的方法,例如
@OnTimer(4000)
表示每间隔4000毫秒执行一次。 - @OnError:当BTrace脚本中其他任何跟踪行为发生异常时,该注解修饰的方法会被执行。
- @OnExit:当BTrace脚本使用代码
BTraceUtils.Sys.exit(0)
来退出BTrace命令行会话时,该注解修饰的方法会被执行。 - @OnEvent:当在BTrace客户端命令行下发生外部事件时,该注解修饰的方法会被执行,此注解的值为事件名称,默认值为"SIGINT"。目前,当执行
ctrl+c
操作时,该注解修饰的方法就会被执行。 - @OnLowMemory:用来监测JVM中堆内存是否达到阈值,它有两个属性
pool
、threshold
,pool
指定监测年轻代还是老年代,GC算法不同也会有所不同,threshold
指定阈值大小,举例@OnLowMemory(pool = "Tenured Gen",threshold=6000000)
- @OnProbe:该注解可以用来避免使用BTrace脚本的内部类,需要和xml映射文件配合使用,详见User's Guide.
拦截时机
@OnMethod注解的location
属性指明拦截目标代码的拦截时机,经常使用的有如下几个:
- Kind.ENTRY:刚进入目标方法时就执行,当没有指明
location
时,此为默认值。 - Kind.RETURN:目标方法返回时执行,配合方法参数注解@Duration可以获取目标方法执行时间,单位纳秒。
- Kind.THROW:目标方法有异常被抛出时。
- Kind.CATCH:目标方法有异常被捕获时。
- Kind.ERROR:目标方法有异常没被捕获抛出了方法之外。
- Kind.CALL:目标方法被调用时。
- Kind.LINE:目标方法具体某一行被执行时,值为-1表示方法内所有代码行。
方法参数注解
- @ProbeClassName:目标代码的类名,全限定名,@OnMethod的
clazz
中定义。 - @ProbeMethodName:目标代码的方法名,@OnMethod的
method
中定义。 - @Duration:目标方法执行时间,与
Kind.RETURN
配合使用。 - @Return:目标方法返回值,与
Kind.RETURN
配合使用。。 - @Self:目标实例,指向
this
关键字的值。 - @TargetInstance:与
Kind.CALL
一起使用,代表location
中定义的监控到的调用实例。 - @TargetMethodOrField:与
Kind.CALL
一起使用,代表location
中定义的监控到的调用方法与属性。
属性注解
- @TLS:ThreadLocal变量,可用于在多个拦截方法间通信、共享变量,只能在使用@OnMethod注解的拦截方法中访问。
- @Export:该注解标注的字段可以被映射到一个jvmstat计数器上,用以暴露给外部的jvmstat客户端(例如jstat)。
使用示例
获取执行时间超过100ms的方法
@BTrace
public class DurationDemo {
@OnMethod(
clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
method="/.*/",
location = @Location(Kind.RETURN)
)
public static void duration(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long duration) {
//duration单位纳秒
if(duration > 1000000 * 100){
BTraceUtils.println("DurationDemo.duration: " + pcn + "." + pmn + ":" + (duration/1000000) + "ms");
}
}
}
目标方法的某一行是否被执行
@BTrace
public class AllLines {
@OnMethod(
clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
method = "createUser",
location=@Location(value=Kind.LINE, line=24)
)
public static void applines(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
BTraceUtils.println("AllLines.applines: " + pcn + "." + pmn + ":" + line);
}
}
获取目标方法的参数
@BTrace
public class ArgArray {
@OnMethod(
clazz="/com\\.cellei\\.btrace\\.practice\\.controller\\..*/",
method="updateUser",
location = @Location(Kind.ENTRY)
)
public static void userArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id, User user) {
//只能使用BTraceUtils中的反射
Field filed = BTraceUtils.field("com.cellei.btrace.practice.model.User", "name");
BTraceUtils.println("ArgArray.userArg<id>: " + id);
BTraceUtils.println("ArgArray.userArg<user>: " + user);
BTraceUtils.printFields(user);
BTraceUtils.println("ArgArray.userArg<user.name>:" + BTraceUtils.get(filed, user));
}
}
获取构造方法的参数及调用栈
@BTrace
public class Constructor {
@OnMethod(
clazz="com.cellei.btrace.practice.model.User",
method="<init>"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.printArray(args);
BTraceUtils.jstack();
}
}
目标方法重载时
@BTrace
public class Overload {
@OnMethod(
clazz = "com.cellei.btrace.practice.controller.AppController",
method = "getUser"
)
public static void oneArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String id) {
BTraceUtils.println("Overload.oneArg: " + id);
}
@OnMethod(
clazz = "com.cellei.btrace.practice.controller.AppController",
method = "getUser"
)
public static void twoArg(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name, Integer age) {
BTraceUtils.println("Overload.twoArg: " + name + ":" + age);
}
}
是否有死锁
@BTrace
public class Deadlock {
@OnTimer(4000)
public static void print() {
BTraceUtils.deadlocks();
}
}
获取环境变量及JVM参数
@BTrace
public class JInfo {
static {
BTraceUtils.println("System Properties:");
BTraceUtils.printProperties();
BTraceUtils.println("VM Flags:");
BTraceUtils.printVmArguments();
BTraceUtils.println("OS Enviroment:");
BTraceUtils.printEnv();
BTraceUtils.exit(0);
}
}
捕获异常堆栈
@BTrace
public class OnThrow {
@TLS
static Throwable currentException;
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow(@Self Throwable self) {
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s) {
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s, Throwable cause) {
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow2(@Self Throwable self, Throwable cause) {
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>",
location=@Location(Kind.RETURN)
)
public static void onthrowreturn() {
if (currentException != null) {
BTraceUtils.Threads.jstack(currentException);
BTraceUtils.println("=====================");
currentException = null;
}
}
}
5秒后退出
@BTrace
public class ProbeExit {
private static volatile int i;
@OnExit
public static void onexit(int code) {
BTraceUtils.println("BTrace program exits!");
}
@OnTimer(1000)
public static void ontime() {
BTraceUtils.println("hello");
i++;
if (i == 5) {
BTraceUtils.Sys.exit(0);
}
}
}
基于BTrace监控调试Java代码的更多相关文章
- 远程debug调试java代码
远程debug调试java代码 日常环境和预发环境遇到问题时,可以用远程调试的方法本地打断点,在本地调试.生产环境由于网络隔离和系统稳定性考虑,不能进行远程代码调试. 整体过程是通过修改远程服务JAV ...
- UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现
UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现 测试数据 java代码 package com.hzf.spark.study; import ...
- UserView--第一种方式set去重,基于Spark算子的java代码实现
UserView--第一种方式set去重,基于Spark算子的java代码实现 测试数据 java代码 package com.hzf.spark.study; import java.util.Ha ...
- Eclipse远程调试Java代码的三种方法
Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...
- 如何使用 Idea 远程调试 Java 代码
起因 这几天,我做的项目中需要使用第三方的 API,在第三方的 API 回调时,出现各种错误,需要远程调试.之前做远程调试的时候,我只会在代码中输出日志,记录下来做分析处理,但这样做既麻烦又费时,往往 ...
- 如何在Eclipse中Debug调试Java代码
背景 有的时候你想debug调试Java的源代码,就想试图在Java源代码中设置断点,在Eclipse中常常会出现Unable to insert breakpoint Absent Line Num ...
- 调试Java代码(Eclipse)汇总
Java 10个调试技巧(基础❤❤❤❤❤) Eclipse断点调试(和上一篇基本类似,补充❤❤) 使用Eclipse开发和调试java程序(从安装eclipse开始,特别细,有设置条件断点,回退的具体 ...
- 【Java】使用IDE开发工具远程调试Java代码
概述 服务端程序运行在一台远程服务器上,我们可以在本地服务端的代码(前提是本地的代码必须和远程服务器运行的代码一致)中设置断点,每当有请求到远程服务器时时能够在本地知道远程服务端的此时的内部状态 测试 ...
- Eclipse中debug调试java代码一直报Source not found的解决办法
今天使用eclipse的debug调试代码,一直没法正常调试,一按F6就提示Source not found 根据提示发现可能是另一个项目影响了,所以把另一个项目Close Project,这次直接t ...
随机推荐
- [洛谷P3254] [网络流24题] 圆桌游戏
Description 假设有来自m 个不同单位的代表参加一次国际会议.每个单位的代表数分别为ri (i =1,2,--,m). 会议餐厅共有n 张餐桌,每张餐桌可容纳ci (i =1,2,--,n) ...
- pywin32 获取 windows 的窗体内文本框的内容
用 spy++去确认找到了文本框的句柄了. 用函数 win32gui.SendMessage 获取不了文本框的文本内容,用 str 类型的参数接收获取的内容的话没有获取到东西,而用 PyBuffer ...
- xsd 和 wsdl
xsd : 可用方便 不同的语言之间的 用命令行来 转换对应语言的. wsdl: 可用方便不同语言的类描述 用命令行 来相互转换. 类似 thift me ?
- 分析一下 原型模式的 UML 类图 。 复制对象, 深浅拷贝 月经贴 ,请回避
- python,for循环的使用案例集
1.循环执行某一系列操作.将该操作定义为一个def,然后使用for去循环执行该操作 思路,先把操作定义为一个函数,在for循环执行这个函数 比如下面案例,把微信好友列表内的好友,循环的方式依次调整到第 ...
- python的数据类型之列表list
列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现.例如: stus = ["zhangsan","lisi","wangwu ...
- Redis(八):zset/zadd/zrange/zrembyscore 命令源码解析
前面几篇文章,我们完全领略了redis的string,hash,list,set数据类型的实现方法,相信对redis已经不再神秘. 本篇我们将介绍redis的最后一种数据类型: zset 的相关实现. ...
- ReactNative---android系统中Image组件无默认图片问题
react native的Image组件通过网络地址加载图片的时候,若加载失败iOS有默认图片等属性,但安卓没有:但可以通过其他方式来实现: {Platform.OS == 'android'?< ...
- Web 开发工具类(5) | DateUtils
日期工具类 import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFo ...
- pymysql连接提示format: a number is required, not str
最近想随手写一个简单的员工管理系统,第一次使用python连接数据库,在这个过程中就遇到了一些问题,遂记录 遇到问题习惯性百度一下,很多教程都不适合新手,有些还不知道是不是瞎写的,所以我觉得有必要自己 ...