【转】动态字节码技术跟踪Java程序

简介:

 Whats is Java Agent?   .. java.lang.instrument.Instrumentation

 

之前有写 基于AOP的日志调试 讨论一种跟踪Java程序的方法, 但不是很完美.后来发现了 Btrace , 由于它借助动态字节码注入技术 , 实现优雅且功能强大.
只不过, 用起来总是磕磕绊绊的, 时常为了跟踪某个问题, 却花了大把的时间调试Btrace的脚本. 为此, 我尝试将几种跟踪模式固化成脚本模板, 待用的时候去调整一下正则表达式之类的.
跟踪过程往往是假设与验证的螺旋迭代过程, 反复的用BTrace跟踪目标进程, 总有那么几次莫名其妙的不可用, 最后不得不重启目标进程. 若真是线上不能停的服务, 我想这种方式还是不靠谱啊.
为此, 据决定自己的搞个用起来简单, 又能良好支持反复跟踪而不用重启目标进程的工具.

AOP

AOP是Btrace, jip1等众多监测工具的核心思想, 用一段代码最容易说明:

?
1
2
3
4
5
public void say(String words){
   Trace.enter();
   System.out.println(words);
   Trace.exit();
}

如上, Trace.enter() 和 Trace.exit() 将say(words)内的代码环抱起来, 对方法进出的进行切面的处理, 便可获取运行时的上下文, 如:

  • 调用栈
  • 当前线程
  • 时间消耗
  • 参数与返回值
  • 当前实例状态

实现的选择

实现切面的方式, 我知道的有以下几种:

代理(装饰器)模式

设计模式中装饰器模式和代理模式, 尽管解决的问题域不同, 代码实现是非常相似, 均可以实现切面处理, 这里视为等价. 依旧用代码说明:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Person {
   void say(String words);
}
 
class Officer implements Person {
   public void say(String words) { lie(words); }
   private void lie(String words) {...}
}
 
class Proxy implements Person {
   private final Officer officer;
   public Proxy(Officer officer) { this .officer = officer; }
   public void say(String words) {
     enter();
     officer.say(words);
     exit();
   }
   private void enter() { ... }
   private void exit() { ... }
}
 
Person p = new Proxy( new Officer());

很明显, 上述enter() 和exit()是实现切面的地方, 通过获取Officer的Proxy实例, 便可对Officer实例的行为进行跟踪. 这种方式实现起来最简单, 也最直接.

Java Proxy

Java Proxy是JDK内置的代理API, 借助反射机制实现. 用它来是完成切面则会是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ProxyInvocationHandler implements InvocationHandler {
   private final Object target;
   public ProxyInvocationHandler(Object target) { this .target = target;}
   public Object handle(Object proxy, Method method, Object[] args) {
     enter();
     method.invoke(target, args);
     exit();
   }
   private void enter() { ... }
   private void exit() { ... }
}
ClassLoader loader = ...
Class<?>[]  interfaces = {Person. class };
Person p = (Person)Proxy.newInstance(loader, interfaces, new ProxyInvocationHandler( new Officer()));

相比较上一中方法, 这种不太易读, 但更为通用, 对具体实现依赖很少.

AspectJ

AspectJ是基于字节码操作(运行时利用ASM库)的AOP实现, 相比较Java proxy, 它会显得对调用更”透明”, 编写更简明(类似DSL), 性能更好. 如下代码:

?
1
2
3
pointcut say(): execute(* say(..))
before(): say() { ... }
after() : say() { ... }

Aspectj实现切面的时机有两种: 静态编译和类加载期编织(load-time weaving). 并且它对IDE的支持很丰富.

CGlib

与AspectJ一样CGlib也是操作字节码来实现AOP的, 使用上与Java Proxy非常相似, 只是不像Java Proxy对接口有依赖, 我们熟知的Spring, Guice之类的IoC容器实现AOP都是使用它来完成的.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
class Callback implements MethodInterceptor {
   public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
     enter();
     proxy.invokeSuper(obj, args);
     exit();
   }
   private void enter() { ... }
   private void exit() { ... }
}
Enhancer e = new Enhancer();
e.setSuperclass(Officer. class );
e.setCallback( new Callback());
Person p = e.create();

字节码操纵

上面四种方法各有适用的场景, 但唯独对运行着的Java进程进行动态的跟踪支持不了, 当然也许是我了解的不够深入, 若有基于上述方案的办法还请不吝赐教.

还是回到Btrace的思路上来, 在理解了它借助java.lang.Instrumentation进行字节码注入的实现原理后, 实现动态变化跟踪方式或目标应该没有问题.

借下来的问题, 如何操作(注入)字节码实现切面的处理. 可喜的是, “构建自己的监测工具”一文给我提供了一个很好的切入点. 在此基础上, 经过一些对ASM的深入研究, 可以实现:

  • 方法调用进入时, 获取当前实例(this) 和 参数值列表;
  • 方法调用出去时, 获取返回值;
  • 方法异常抛出时, 触发回调并获取异常实例.

其切面实现的核心代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private static class ProbeMethodAdapter extends AdviceAdapter {
 
     protected ProbeMethodAdapter(MethodVisitor mv, int access, String name, String desc, String className) {
       super (mv, access, name, desc);
       start = new Label();
       end = new Label();
       methodName = name;
       this .className = className;
     }
 
     @Override
     public void visitMaxs( int maxStack, int maxLocals) {
       mark(end);
       catchException(start, end, Type.getType(Throwable. class ));
       dup();
       push(className);
       push(methodName);
       push(methodDesc);
       loadThis();
       invokeStatic(Probe.TYPE, Probe.EXIT);
       visitInsn(ATHROW);
       super .visitMaxs(maxStack, maxLocals);
     }
 
     @Override
     protected void onMethodEnter() {
       push(className);
       push(methodName);
       push(methodDesc);
       loadThis();
       loadArgArray();
       invokeStatic(Probe.TYPE, Probe.ENTRY);
       mark(start);
     }
 
     @Override
     protected void onMethodExit( int opcode) {
       if (opcode == ATHROW) return ; // do nothing, @see visitMax
       prepareResultBy(opcode);
       push(className);
       push(methodName);
       push(methodDesc);
       loadThis();
       invokeStatic(Probe.TYPE, Probe.EXIT);
     }
 
     private void prepareResultBy( int opcode) {
       if (opcode == RETURN) { // void
         push((Type) null );
       } else if (opcode == ARETURN) { // object
         dup();
       } else {
         if (opcode == LRETURN || opcode == DRETURN) { // long or double
           dup2();
         } else {
           dup();
         }
         box(Type.getReturnType(methodDesc));
       }
     }
 
     private final String className;
     private final String methodName;
     private final Label start;
     private final Label end;
}

更多参考请见这里的 Demo , 它是javaagent, 在伴随宿主进程启动后, 提供MBean可用jconsole进行动态跟踪的管理.

后续的方向

  1. 提供基于Web的远程交互界面;
  2. 提供基于Shell的本地命令行接口;
  3. 提供Profile统计和趋势输出;
  4. 提供跟踪日志定位与分析.

参考

  1. The Java Interactive Profiler
  2. Proxy Javadoc
  3. Aspectj
  4. CGlib
  5. BTrace User’s Guide
  6. java动态跟踪分析工具BTrace实现原理
  7. 构建自己的监测工具
  8. ASM Guide
  9. 常用 Java Profiling 工具的分析与比较
  10. AOP@Work: Performance monitoring with AspectJ
  11. The JavaTM Virtual Machine Specification
  12. 来自rednaxelafx的JVM分享, 他的 Blog
  13. BCEL
原文链接:[http://wely.iteye.com/blog/2329839 ]
相关文章
|
2天前
|
搜索推荐 算法 Java
滚雪球学Java(29):数组长度和排序算法:让你的程序更高效
【5月更文挑战第4天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 0
滚雪球学Java(29):数组长度和排序算法:让你的程序更高效
|
2天前
|
机器学习/深度学习 前端开发 Java
Java与前端:揭开技术浪潮背后的真相
Java与前端:揭开技术浪潮背后的真相
10 1
|
2天前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
23 4
|
2天前
|
前端开发 Java 应用服务中间件
【异常解决】java程序连接MinIO报错The request signature we calculated does not match the signature you provided.
【异常解决】java程序连接MinIO报错The request signature we calculated does not match the signature you provided.
15 0
|
2天前
|
Java Linux C语言
一步带你了解java程序逻辑控制
一步带你了解java程序逻辑控制
15 2
|
2天前
|
Java 数据安全/隐私保护
java中程序控制的典例
java中程序控制的典例
13 1
|
2天前
|
Kubernetes Java 调度
Java容器技术:Docker与Kubernetes
Java容器技术:Docker与Kubernetes
19 0
|
2天前
|
存储 安全 Java
深入理解Java字节码与反编译技术
深入理解Java字节码与反编译技术
18 0
|
2天前
|
缓存 监控 算法
Java程序性能优化策略与实践
在当今软件开发领域,Java作为一种广泛应用的编程语言,其程序性能优化显得尤为重要。本文将介绍一些Java程序性能优化的策略和实践,帮助开发者提高代码执行效率、减少资源消耗,并优化用户体验。通过深入探讨各种优化技术和工具,读者将能够更好地理解和运用这些策略,有效提升Java应用程序的性能。
10 1
|
6月前
|
Java 程序员
竟有阿里大牛用678页PDF只讲Java程序性能优化,除了干货就是干货
Java程序性能优化虽然是一个老生常谈的话题,但想要把这个话题真正讲解清楚却并不容易。目前,市面上为数不多的讲解Java程序性能优化的图书内容不够深入,讲解也不够浅显易懂,有的甚至晦涩难懂。