`
IXHONG
  • 浏览: 437588 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

    博客分类:
  • 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
分享到:
评论

相关推荐

    xalanjava源码-rcjp:使用ASM库检测Java字节码,实现一个简单的引用计数模型来测试Java程序

    本项目旨在从Java程序中发现Whole-Part关系中存在的一些详细属性。 构建检测程序以跟踪 Java 程序中实例的行为并生成跟踪文件。 它可以在某些类加载到 JVM 时修改 Java 字节码。 关系分析基于跟踪文件。 在分析中,...

    java面试八股文2023完整版110题附带答案

    JVM通过将Java字节码转换为本地机器码来运行Java程序。JVM还提供了内存管理和垃圾回收机制,确保Java程序的内存安全和高效性。 3. 什么是Java的垃圾回收?其工作原理是什么? Java的垃圾回收机制是Java内存管理的一...

    java源码包---java 源码 大量 实例

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    论文研究-基于自动机的Java信息流分析.pdf

    首先,提出了基于有限状态自动机的Java信息流分析方法,将整个程序变量污点取值空间抽象为自动机状态空间,并将Java字节码指令看做自动机状态转换动作;然后,给出了自动机转换的信息流安全规则,并证明了在该规则下...

    JAVA上百实例源码以及开源项目

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    Arthas开源的Java诊断工具.rar

    Arthas 的基本原理是使用 Java Agent 实现的,它会在应用程序启动时向 JVM 中注入一个 Java Agent,该 Agent 可以修改字节码,动态地为应用程序增加一些代码。通过这种方式,Arthas 可以在应用程序运行时,动态地...

    JAVA上百实例源码以及开源项目源代码

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    java源码包4

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    java源码包3

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    btrace:BTrace-用于Java平台的安全,动态跟踪工具

    BTrace动态地检测目标应用程序的类以注入跟踪代码(“字节码跟踪”)。 学分 基于 由提供支持 由提供支持 使用优化 使用构建环境助手 建筑BTrace 建立 您将需要安装以下应用程序 JDK -JDK 8,Java 9和Java 11必须...

    java开源包11

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包6

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包9

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包4

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包101

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java-tracing-agent:Java运行时动态跟踪

    Java跟踪代理使用JVM中包含的Java Instrumentation支持来修改现有的类字节码。 在加载类时,将在执行之前对其进行修改。 如果我们更改跟踪配置,也可以在运行时修改类。 特征 运行时更新 可以在应用程序启动和运行...

    java源码包2

     Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。  设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang...

    java开源包5

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包8

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

    java开源包10

    在浏览器上运行Java程序 Doppio DoppioVM 是一个可在浏览器上直接运行 Java 项目的系统,无需任何插件支持。目前它包含一个完整的虚拟机以及一个 javap 字节码反汇编器。 brap(Java远程调用框架 BRAP) 一个Java远程...

Global site tag (gtag.js) - Google Analytics