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。
  1. clazz 属性用来指定目标代码的类名,支持全限定名/正则/父类/注解形式。全限定名,例如java.lang.Thread;也可以使用正则表达式匹配,例如/java\\.lang\\..+/;父类或接口的形式,例如+java.lang.Runnable,注解形式,例如@javax.annotation.Resource。内部类使用$符号连接。
  2. method 属性用来指定目标代码的方法名,支持全限定名/正则/注解形式,特殊的,指定构造方法时使用<init>表示,目标类有重载的方法时,根据@OnMethod修饰的方法的参数列表具体指明哪个目标方法。
  3. 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代码的更多相关文章

  1. 远程debug调试java代码

    远程debug调试java代码 日常环境和预发环境遇到问题时,可以用远程调试的方法本地打断点,在本地调试.生产环境由于网络隔离和系统稳定性考虑,不能进行远程代码调试. 整体过程是通过修改远程服务JAV ...

  2. UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现

      UserView--第二种方式(避免第一种方式Set饱和),基于Spark算子的java代码实现   测试数据 java代码 package com.hzf.spark.study; import ...

  3. UserView--第一种方式set去重,基于Spark算子的java代码实现

    UserView--第一种方式set去重,基于Spark算子的java代码实现 测试数据 java代码 package com.hzf.spark.study; import java.util.Ha ...

  4. Eclipse远程调试Java代码的三种方法

    Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...

  5. 如何使用 Idea 远程调试 Java 代码

    起因 这几天,我做的项目中需要使用第三方的 API,在第三方的 API 回调时,出现各种错误,需要远程调试.之前做远程调试的时候,我只会在代码中输出日志,记录下来做分析处理,但这样做既麻烦又费时,往往 ...

  6. 如何在Eclipse中Debug调试Java代码

    背景 有的时候你想debug调试Java的源代码,就想试图在Java源代码中设置断点,在Eclipse中常常会出现Unable to insert breakpoint Absent Line Num ...

  7. 调试Java代码(Eclipse)汇总

    Java 10个调试技巧(基础❤❤❤❤❤) Eclipse断点调试(和上一篇基本类似,补充❤❤) 使用Eclipse开发和调试java程序(从安装eclipse开始,特别细,有设置条件断点,回退的具体 ...

  8. 【Java】使用IDE开发工具远程调试Java代码

    概述 服务端程序运行在一台远程服务器上,我们可以在本地服务端的代码(前提是本地的代码必须和远程服务器运行的代码一致)中设置断点,每当有请求到远程服务器时时能够在本地知道远程服务端的此时的内部状态 测试 ...

  9. Eclipse中debug调试java代码一直报Source not found的解决办法

    今天使用eclipse的debug调试代码,一直没法正常调试,一按F6就提示Source not found 根据提示发现可能是另一个项目影响了,所以把另一个项目Close Project,这次直接t ...

随机推荐

  1. Qt Installer Framework翻译(7-2)

    包文件夹 安装程序包含的组件,要么是内嵌的,要么可以从远程存储库加载.在这两种情况下,都需要为组件使用一种安装程序可以读取的文件格式和结构. 包文件夹结构 将所有组件放在相同的根文件夹中,即包文件夹. ...

  2. tensorflow框架

    一.tensorflow的工作流程,实际上它体现出来的是一个”懒性“方法论 (1)构建一个计算图. (2)初始化变量 (3)创建一个会话 (4)在会话中运行图的计算 (5)关闭会话 二.神经网络搭建八 ...

  3. FastDF step by step

    step one 肯定是安装一个FastDF服务了 step two FasDFS配置节点 step third 码代码

  4. Java and MongoDB link for project

    鉴于开源项目的发展,大力拥抱开源社区.发现Java和MongoDB不失为一个较好的选择. 与其他数据库一样,同样需要mongo-java-driver,构建了Java与MongoDB的交互. 1. 连 ...

  5. git hub安装

    windows下GitHub的安装.配置以及项目的上传过程详细介绍 阅读目录 概要 操作必备 GitHub的安装 Git的初始配置 本地Git与远程GitHub连接的建立 将本地项目上传到GitHub ...

  6. springboot开发之使用外部servlet容器及对jsp的支持

    一般而言,springboot是使用自己内嵌的servlet容器,比如tomcat等等,而且默认的模板引擎是thymeleaf,那么如何让springboot使用外部的servlet容器并支持对jsp ...

  7. RSYNC 同步工具

    Rsync 数据同步工具 一.Rsync简介 1.检测一下你的机器上是否已经安装  --->可以通过yum安装  或者  源码 Server  192.168.201.151 Client    ...

  8. Docker深入浅出系列 | 容器初体验

    目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...

  9. 【vue_django】成功登录后,保存用户

    PS:使用session 保存: // 登录 login() { if (this.username === "" || this.password === "" ...

  10. Bash 脚本中的 set -euxo pipefail

    有些开发人员会用Bash来实现很复杂的功能,就像使用别的高级语言一样.他可能觉得自己很牛逼但其他人早就想锤爆他了,Bash的可读性和可维护性远远低于任何高级语言.更要命的是,Bash并没有方便的调试工 ...