BTrace分析和使用
一、 BTrace简介
BTrace是一个为Java平台开发的安全、动态的追踪工具。BTrace动态地向目标应用程序的字节码注入Java追踪代码(字节码追踪)。
GitHUB地址https://github.com/btraceio/btrace。
原理为将字节码发送到应用,并通过Instrumentation使用asm修改对应的应用class字节码,注入特定逻辑。
二、 对应用影响
影响仅在于应用内部会加载BTrace类,拦截的class会稍变化。
BTrace启动后,会对拦截的class进行字节码修改,但由于BTrace的主旨在于read-only,所以不会对class生成对象的内部状态做出更改,而且,BTrace退出后,Action不会被执行。
三、 解决的问题
特色功能为动态增加拦截处理器字节码到JVM,监控方法的调用和被调用,其中包括普通方法调用,以及构造方法调用。
非特色功能包括定时任务、随时打印线程堆栈、查看某个对象锁持有状态、某个线程状态、jvm参数、堆dump、即时检测死锁、显示内存占用等等jvisualvm包括的技术,因为其实现的方式类似。
四、 不能做的事情
Ø 不能新建类、新建数组、 抛异常、 捕获异常,
Ø 不能调用实例方法以及静态方法(com.sun.btrace.BTraceUtils除外)
Ø 不能将目标程序和对象赋值给BTrace的实例和静态field
Ø 不能定义外部、 内部、匿名、本地类、不能实现接口、 不能扩展类
Ø 不能有同步块和方法
Ø 不能有循环
Ø 不能使用assert语句
Ø 不能使用class字面值
为了保证trace语句只读, BTrace对trace脚本有一些限制(比如不能改变被trace代码中的状态),这些限制同时也保证了可以放心使用Btrace,而无需担心影响应用内部状态。
五、 实例
@BTrace
public class ArgumentsAction {
@Export
private static int count = 0;
private static Map String> history = Collections.newWeakMap(); @OnMethod(clazz = "/btrace\\.sub\\..*/", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/"), enableAt = @Level("=0")) public static void on(@Self Object self, @TargetInstance Object instance, @ProbeClassName String tar, @ProbeMethodName String pmn, AnyType[] args) { BTraceUtils.printArray(args); Collections.put(history, tar, pmn); } @OnTimer(1000) public static void print() { BTraceUtils.println(Collections.size(history)); } @OnEvent("l0") public static void event() { BTraceUtils.setInstrumentationLevel(0); } } 运行参数btrace 此实例已经几乎包含所有知识点,具体包含以下几点: Ø @BTrace、@Export、@OnMethod,即Class注解、字段注解、方法注解; Ø Collections.newWeakMap()创建及使用Map只能通过BTraceUtils内部类操作; Ø @Self、@TargetInstance、AnyType,可以获取调用对象、被调用对象、参数; Ø BTraceUtils.print等工具方法,输出信息只可以通过BTraceUtils提供的工具方法; Ø @OnTimer定时器,创建定时任务; Ø @OnEvent接收Btrace应用的事件(通过按下Ctrl+c); 六、 使用详解 首先带来三个概念,完全可以按照Aop的思想理解。 Probe Point :在何处执行trace语句, 这里的"何处"可以是具体的跟踪地点和执行事件, 在BTrace中通过各种注解来指定; Trace Actions or Actions :在何时执行trace语句; Action Methods :定义在trace脚本中的trace语句, 具体来说就是脚本中的无返回值静态方法; A) @OnMethod,放在Action Method上,创建行动; a) class属性。拦截的对象的类名,可以为全类名,如btrace.action.ArgumentAction;也可以为正则表达式,如/btrace\\.sub\\..*/,注意正则表达式必须写在//中; b) method属性。拦截对象的方法名称,参数同样如同class属性; c) type属性。如果拦截方法有重载,可以设置仅拦截特定重载方法。如type="void (int, int, java.net.InetAddress)"; d) enableAt属性。此属性代表设置该Action执行等级,如同Log的日志等级。如设置为@Level(">2"),则通过BTraceUtils.setInstrumentationLevel(3)之后,此Action才会执行; e) location属性@Location; f) value属性包括为Kind枚举值。其中常用为ENTRY、CALL、CATCH,其他请查看com.sun.btrace.annotations.Kind注释,每个枚举值有其详细的使用方式; g) Where属性,如Kind.ARRAY_GET类型,其严格要求where必须为AFTER; h) class和method含义如同@OnMethod相同,但生效时机有时不同,如使用Kind.CALL时,此过滤主动调用的对象。 i) field属性,是是用Kind. FIELD_GET和Kind. FIELD_SET时字段的名称; j) line属性,代表到达代码第几行触发; B) @OnEvent在客户端按下Ctrl+c; 1:exit#直接退出 2:send an event #触发无参数@OnEvent 3:send a named event #输入事件名,触发@OnEvent(“ C) @OnTimer创建定时任务; D) @com.sun.btrace.annotations.OnError Action内部发生错误触发; E) @com.sun.btrace.annotations.OnExit Action内部调用BTraceUtils.exit调用触发; F) @com.sun.btrace.annotations.OnLowMemory内存低时触发; G) @Property创建属性为btrace. H) @Export添加虚拟机字段,可以通过jstat -J-Djstat.showUnsupported=true -snap 七、 Btrace内部使用简单分析; 使用技术包括asm、instrument、JVM TI(java tool api) 、Java Compiler Api ,简单流程图如下,其功能与jvisualvm方式类似,可以在JVM参数添加-verbose查看类加载过程。 如果需要查看Btrace的debug信息,可以在修改btrace脚本,添加-Dcom.sun.btrace.debug=true。instrument即为javaagent,其可以直接修改字节码,这也是Btrace的必要技术,查看修改的类的dump文件可以添加参数-Dcom.sun.btrace.dumpClasses=true -Dcom.sun.btrace.dumpDir=path。 JVM TI是JPDA的一部分,而我们常用的jdi、jdwp则与JVM TI处于同一等级。在使用中会发现,可以多次修改Action,这就牵涉到热更新的一部分,与Eclipse实现热更新实现的方式在jvm内部处理是相同的,都是通过Instrumentation实现,而这种实现的方式在于,仅可以修改已存在的方法体。