Java开发大型互联网架构深入解析BTrace 介绍及原理实践

引言

BTrace是Java的安全可靠的动态跟踪工具。 他的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强。

说他是安全可靠的,是因为它对正在运行的程序是只读的。也就是说,他可以插入跟踪语句来检测和分析运行中的程序,不允许对其进行修改。因此他存在一些限制:

·不能创建数组

·不能抛出和捕获异常

·不能调用任何对象方法和静态方法

·不能给目标程序中的类静态属性和对象的属性进行赋值

·不能有外部、内部和嵌套类

·不能有同步块和同步方法

·不能有循环(for, while, do..while)

·不能继承任何的类

·不能实现接口

·不能包含assert断言语句

这些限制其实是可以使用unsafe模式绕过。通过声明 *@BTrace(unsafe = true) annotation 并且以unsafe 模式-u运行btrace

实际使用非安全模式跟踪时,发现一个问题,一个进程如果被安全模式btrace探测过一次, 后面再使用非安全模式进行探测时非安全模式不生效。

安装并使用

·下载Btrace并解压

·定义环境变量BTRACE_HOME为解压目录

·bin目录加入到path

运行BTrace

btrace

·pid 为java进程号,可以使用jps来查询

·btrace-script 为 btrace脚本

也可以先使用btracec对btrace脚本继续预先编译再进行执行。

visualvm-plugin

btrace还提供了一个vaisualvm上的一个插件,可以执行btrace脚本。尝试了下,可以attach到本机的jvm进程上,但是远程主机的JVM进程不行。晚上有的说通过端口转发绑定的方式可以,但是还是没有试出来。

安装

运行jvisualvm.exe, 选择工具->插件->可用插件 选择 BTrace Workbench进行在线安装。

jvisualvm_btrace.jpg

在线不能安装的,也可以通过手动安装。先从java visualvm 插件中心找到相应版本的插件更新文件地址进行下载,从更新文件中获取所需要的插件包的nbm下载地址进行下载。最后在 插件->已下载tab->添加插件

执行btrace脚本

选择需要监控的进程,右击 trace application

jvisualvm_btrace2.png

在btrace的工作台中直接编写脚本并执行

jvisualvm_btrace3.png

BTrace 脚本

先来看简单的BTrace脚本的使用,用于跟踪方式执行时间

@BTrace

public class TimeLogger {

@TLS private static long startTime = 0;

@OnMethod(clazz="org.springframework.data.redis.core.DefaultValueOperations", method="get")

public static void startExecute(){

startTime = timeNanos();

}

@OnMethod(clazz="org.springframework.data.redis.core.DefaultValueOperations", method="get",

location=@Location(Kind.RETURN)

)

public static void endExecute(@Duration long duration){

long time = timeNanos() - startTime;

println(strcat("execute time(nanos): ", str(time)));

}

}

·@BTrace 声明了这个类是BTrace脚本

·@OnMethod 声明了关注点,必须声明在公有的静态方法上public static void

·静态方法为 在关注点上执行的跟踪动作

跟踪方式执行时间程序的逻辑大致是: 在org.springframework.data.redis.core.DefaultValueOperations 对象执行get方法时,先记录一下当前时间,在get方法return时获取当前时间,从而获得方法执行所消耗的时间。值得注意的是,@TLS声明的变量是 ThreadLocal的, 每个线程都会有一份这个自己的startTime 变量。

执行以上的TimeLogger来看一下:

$ btrace 13778 TimeLogger.java

execute time(nanos): 214692000

execute time(nanos): 1215000

execute time(nanos): 3210000

执行后,当被监控的程序运行了这些检查点的方法时,btrace会在控制台对执行时间进行输出。通过重定向符>也可以将输出重定向到文件.

那么,BTrace这么神奇的功能是如何实现的呢?既然这是个开源的代码,那么直接从代码找原理。https://github.com/btraceio/btrace。

总体来说,BTrace是基于动态字节码修改技术(Hotswap)来实现运行时java程序的跟踪和替换。大体的原理可以用下面的公式描述:

v

Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + JDK6 Instumentation) + Socket

BTrace的入口类在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Main.java中。在其main方法中,可以看到起最终的核心逻辑是在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Client.java中。方法调用如下:

client.compile

client.attach

client.submit

Client

首先是client.compile方法,使用的是Java compile api,将我们传递的java源文件编译为.class文件,当然你如果使用btracec提前编译了源代码,那么这里就不会有这一步。

针对官方脚本的一个例子:

import com.sun.btrace.annotations.*;

import static com.sun.btrace.BTraceUtils.*;

@BTrace

public class HelloWorld {

@OnMethod(

clazz="java.lang.Thread",

method="start"

)

public static void func() {

println("about to start a thread!");

}

}

@OnMethod告诉Btrace解析引擎需要代理的类和方法。 这个例子的作用是当java.lang.Thread类的任意一个对象调用 start 方法后,会调用func方法。

client端在编译完脚本之后,进行了一次字节码修改,但是仅仅是做了一些兼容性,例如域访问控制器、简写等。

接着client.attach中使用java的attach api将agent动态attach到目标jvm进程中(ava agent,通常有两种方式添加到jvm进程中:动态attach;在目标jvm启动之前添加agent参数)。

VirtualMachine vm = VirtualMachine.attach(pid);

...

vm.loadAgent(agentPath, agentArgs);

最后client的submit方法,会向agent发送监控命令以及传递对应code的字节码。

Agent

BTrace的agent实现类就在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/agent/Main.java中,具体的实现可以看其main方法,此agent的premain和agentmain方法都是调用了这个方法。这里需要注意的一点:必须要上jdk6,因为jdk5虽然已经有了instrument api,但是其仅仅支持premain方法,也就是仅仅支持在main方法运行之前执行一些动作,而jdk6后加入了agentmain方法和VirtualMachine,是可以在main方法运行后执行的(如果是通过命令行启动的,那么agentmain方法不会被调用)。此外,在jdk6之前,程序启动之后是无法再设置boot class加载路径和system class加载路径的。而jdk6之后,instrument新增的appendToBootstrapClassLoaderSearch和appendToSystemClassLoaderSearch是可以动态添加classpath的。

agent被提交到目标jvm进程后,首先会添加boot classpath.

...

inst.appendToBootstrapClassLoaderSearch(jf);

...

inst.appendToSystemClassLoaderSearch(jf);

接着开启一个serversocket等待client的连接。之后client和agent之间的数据通讯,比如生成.class发送到agent,agent将线上程序打印的数据回传给 client都是通过socket来进行的。当agent接收到监控命令后,主要有以下两部分工作:

重写类:遍历当前所有的class,根据正则找到匹配的类,用asm重写

替换类:替换掉原来的class

agent接受到client发来的监控指令以及对应的参数后,会load所有的class,根据正则去匹配指定的类和方法,并使用脚本解析引擎去处理发送过来的字节码然后使用ASM将脚本里标注的类java.lang.Thread的字节码重写,植入跟踪代码或新的逻辑。在上面那个例子中,Java.lang.Thread这个类的字节码被重写并在start方法体尾部植入了func方法的调用。

BTrace的agent利用instrumentation的retransformClasses方法将原始字节码替换掉,使用的transfomer见https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/runtime/BTraceTransformer.java。如下:

new ClassFileTransformer() {

public byte[] transform(ClassLoader l, String className, Class c, ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {

// BTrace解析脚本,利用asm重写bytecode,然后classLoader加载

}

}, true);

其中,在agent的agentmain中通过handleNewClient方法启动一个异步线程进行class transformer,而在这个异步线程中最终是通过调用

2.怎样才能方便快捷无误地写出BTrace脚本呢2.1使用VisualVm来写如果你使用VisualVm的话,那么也可以使用VisualVm来编写BTrace脚本。不过需要你安装btrace的插件。在VisualVm中安装插件异常的爽,只需要在插件列表中点击一下就可以了。如下图(由于我已经安装过了,所以不再【可用插件】列表中,在【已安装】列表中)

Java开发大型互联网架构深入解析BTrace 介绍及原理实践_第1张图片
Java开发大型互联网架构深入解析BTrace 介绍及原理实践_第2张图片
Java开发大型互联网架构深入解析BTrace 介绍及原理实践_第3张图片
Java开发大型互联网架构深入解析BTrace 介绍及原理实践_第4张图片

Btrace插件列表.png

BTrac已安装列表.png

安装好BTrace插件之后就可以使用VIsualVm来打开一个java应用程序了。如下打开BTrace脚本的编写界面

打开Btrace插件界面.png

Btrace编写脚本界面.png

在这里面编写BTrace脚本其实和在记事本上编写脚本差不多。哪为啥要在这写呢?

添加classpath比较方便

将脚本绑定到应用程序方便

总结

以 上就是我对Java开发大型互联网架构深入解析BTrace 介绍及原理实践            问题及其优化总结,分享给大家,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!

最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!

进阶地址:https://ke.qq.com/course/230866?flowToken=1000327

想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:(666 必过)欢迎大家的加入哟!

你可能感兴趣的:(Java开发大型互联网架构深入解析BTrace 介绍及原理实践)