【转】动态字节码技术跟踪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]

时间: 2024-10-27 02:48:46

【转】动态字节码技术跟踪Java程序的相关文章

spring ,hibernate 都是用到了asm字节码技术,请问它们具体都拿ASM来实现了什么功能?或者说在哪个功能上用到的

问题描述 spring ,hibernate 都是用到了asm字节码技术,请问它们具体都拿ASM来实现了什么功能?或者说在哪个功能上用到的 我们查看Spring,主要使用了cglib动态代理来实现一些IOC或者AOP功能,但是这个和ASM好像是没关系?还有hibernate,在做数据持久化的时候主要用的是反射,那它有拿ASM干了什么呢? 解决方案 动态代理,hibernate的懒加载使用到了asm,spring的AOP也使用到了.你建立一个hibernate映射对象并使用懒加载配置的时候,在内存

请帮忙把字节码翻译成java代码

问题描述 请帮忙把字节码翻译成java代码 有人能帮忙把下面的字节码翻译成对应的java代码吗,谢谢 // Byte code: // 0: ldc 40 // 2: dup // 3: astore_2 // 4: monitorenter // 5: aconst_null // 6: astore_3 // 7: aconst_null // 8: astore 4 // 10: aconst_null // 11: astore 5 // 13: aload_1 // 14: invok

用java字节码解释i++和++i(转)

这几天抽着一些时间,把Java的class文件结构研究了一下,再后来就想起了这个令人厌烦的问题,想从字节码指令的角度看看,java到底是怎么处理这个的 先看一段java代码   [java] view plain copy   package bishi;      public class PlusPlusTest {              public static void main(String[] args) {              int i = 1;           

Java字节码

原文出处:https://www.ibm.com/developerworks/library/it-haggar_bytecode/index.html#opcode 作者:Peter Haggar 发表时间:2001 / 07 / 01 理解字节码可以使你变成一个更好的程序员 关于字节码的信息,以及这里提供的字节码,都是基于Java 2 SDK标准版v1.2.1 javac编译器的.其他编译器生成的字节码可能略有不同. 为什么要理解字节码? 字节码就是 Java 代码的中间表示,就像是汇编代

关于java字节码框架ASM的学习

一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为.Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称.方法.属性以及 Java 字节码(指令).ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 使用ASM框架需要导入asm的jar包,下载链接:

深入字节码 -- 计算方法执行时间

什么是字节码? java程序通过javac编译之后生成文件.class就是字节码集合,正是有这样一种中间码(字节码),使得scala/groovy/clojure等函数语言只用实现一个编译器即可运行在JVM上. 看看一段简单代码. public long getExclusiveTime() { long startTime = System.currentTimeMillis(); System.out.printf("exclusive code"); long endTime =

JVM层对jar包字节码加密

github https://github.com/sea-boat/ByteCodeEncrypt 需求 拿到的需求是要对某特定的jar包实现加密保护,jar包需要提供给外部使用,但核心逻辑部分需要保护以免被简单反编译即能看到. 几个思路 大致想到以下几种方式: 1. 混淆器,将jar包混淆后反编译出来的东西看起来就很眼花,但如果耐心一点也是可以看出来的. 2. 对jar包进行加密,然后在Java层重写类加载器对其进行解密,以达到对jar包的加密保护.包括用对称加密算法和非对称加密算法.不管用

如何保护Java程序 防止Java反编译

Java是一种跨平台的.解释型语言.Java 源代码编译中间"字节码"存储于class文件中.Class文件是一种字节码形式的中间代码,该字节码中包括了很多源代码的信息,例如变量名.方法名等.因此,Java中间代码的反编译就变得非常容易.目前市场上有许多免费的.商用的反编译软件,都能够生成高质量的反编译后的源代码.所以,对开发人员来说,如何保护Java程序就变成了一个非常重要的挑战.本文首先讨论了保护Java程序的基本方法,然后对代码混淆问题进行深入研究,最后结合一个实际的应用程序,分

10招让你成为杰出的Java程序员(转)

  如果你是一个热衷于技术的 Java 程序员, 那么下面的 10 个要点可以让你在众多 Java 开发人员中脱颖而出. 1. 拥有扎实的基础和深刻理解 OO 原则 对于 Java 程序员,深刻理解 Object Oriented Programming(面向对象编程)这一概念是必须的.没有 OOPS 的坚实基础,就领会不了像 Java 这些面向对象编程语言的美.光学习 OO 原则的定义用处不大,关键是要学会如何应用这些原则用一种 OO 的方式去设计解决方案.因此,我们应该对对象建模.继承.多态