1. Btrace简介
1.1 概述
在周志明老师的深入理解JVM虚拟机一书中,周老师这样描述Btrace:在不停止目标程序运行的情况下,通过HotSpot虚拟机的HotSwap技术动态加入原本并不存在的调试代码。从这段话,我们可以得到以下信息:1.使用Btrace可以在不停止程序的情况下进行调试。2.应用于HotSpot虚拟机,如果是其他虚拟机,是不能用的。
- BTrace可以动态地向目标应用程序的字节码注入追踪代码;
- 用到的技术:JavaComplier JVMTI Agent Instrumentation + ASM;
- 默认只能本地运行,就是只能调试本地的Java进程;
- 生产环境下可以使用,但是被修改的字节码不会被还原;
1.1.1 安装方法
- 配置坏境变量BTRACE_HOME;
- 配置Path,添加%BTRACE_HOME%bin;
1.1.2 两种运行脚本方式
- 在JVisualVM中添加Btrace插件,添加classpath
- 使用命令行btrace <pid> <trace_script>
- 注意BTrace脚本的项目要引入btrace-agent.jar,btrace-boot.jar,btrace-client.jar这3个依赖;
1.2 BTrace实战
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>zte.hdh</groupId>
<artifactId>monitor_tuning</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>monitor_tuning</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-agent</artifactId>
<version>1.3.11.3</version>
<type>jar</type>
<scope>system</scope>
<systemPath>D:\ImportantDevTools\btrace-bin-1.3.11.3\build\btrace-agent.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-boot</artifactId>
<version>1.3.11.3</version>
<type>jar</type>
<scope>system</scope>
<systemPath>D:\ImportantDevTools\btrace-bin-1.3.11.3\build\btrace-boot.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-client</artifactId>
<version>1.3.11.3</version>
<type>jar</type>
<scope>system</scope>
<systemPath>D:\ImportantDevTools\btrace-bin-1.3.11.3\build\btrace-client.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2.1 拦截参数
测试类(被拦截)
BTraceOneController.java:
package zte.hdh.btrace;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/btrace1")
public class BTraceOneController {
@RequestMapping("/arg1")
public String arg1(@RequestParam("name") String name) {
return "hello," + name;
}
}
Btrace类
BTraceArgSimple.java:
package zte.hdh.btrace;
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
@BTrace
public class BTraceArgSimple {
@OnMethod(
clazz="zte.hdh.btrace.BTraceOneController",//拦截的方类
method="arg1",//拦截的方法
location=@Location(Kind.ENTRY)//拦截的实际
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {//AnyType[] args意思是所有参数,如果方法有两个参数String int,那么这里就可写String arg1,int arg2
BTraceUtils.printArray(args);
BTraceUtils.println(pcn);
BTraceUtils.println(pmn);
BTraceUtils.println();
}
}
启动应用后,获取pid,到btrace脚本BTraceArgSimple.java的位置,运行btrace 48284 BTraceArgSimple.java,开始监控我们的程序。在浏览器中输入:http://localhost:8080/btrace1/arg1?name=hdh,那么运行btrace脚本的控制台将输出拦截的参数、类和方法等信息:
1.2.2 拦截方法
- 普通方法 @OnMethod(clazz="",method="")
clazz为完整类名。 - 构造方法 @OnMethod(clazz="",mewthod="<init>")
clazz为完成类名,用<init>的原因是因为构造函数在字节码层面就是这么定义的,而btrace就是在字节码层面进行拦截的。 - 拦截同名函数,用参数进行区分
1.2.3 拦截时机
- Kind.ENTRY:入口,默认值
- Kind.RETURN:返回
- Kind.THROW:异常
- Kind.Line:行
1.2.4 捕获方法返回值
PrintReturn.java
package zte.hdh.btrace;
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
@BTrace
public class PrintReturn {
@OnMethod(
clazz="zte.hdh.btrace.BTraceOneController",
method="arg1",
location=@Location(Kind.RETURN)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Return AnyType result) {
BTraceUtils.println(pcn + ", " + pmn + "," + result);
BTraceUtils.println();
}
}
1.2.5 打印异常信息
BTraceOneController.java中增加一个rest接口:
@RequestMapping("/exception")
public String exception() {
try {
System.out.println("start...");
System.out.println(1/0);
System.out.println("end...");
}catch(Exception e) {
//忘记打印出异常
}
return "success";
}
btrace类PrintOnThrow.java:
package zte.hdh.btrace;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
@BTrace
public class PrintOnThrow {
// store current exception in a thread local
// variable (@TLS annotation). Note that we can't
// store it in a global variable!
@TLS
static Throwable currentException;
// introduce probe into every constructor of java.lang.Throwable
// class and store "this" in the thread local variable.
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow(@Self Throwable self) {//拦截new Throwable()
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s) {//拦截new Throwable(String msg)
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s, Throwable cause) {//拦截new Throwable(String msg, Throwable cause)
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow2(@Self Throwable self, Throwable cause) {//拦截new Throwable(Throwable cause)
currentException = self;
}
// when any constructor of java.lang.Throwable returns
// print the currentException's stack trace.
@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;
}
}
}
1.2.6 检查某行是否执行
package zte.hdh.btrace;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
@BTrace
public class PrintLine {
@OnMethod(
clazz="com.example.demo.btrace.Ch4Controller",
method="exception",
location=@Location(value= Kind.LINE, line=40)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
BTraceUtils.println(pcn + ", " + pmn + "," + line);
BTraceUtils.println();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。