3

动态实时跟踪你的java程序

 2 years ago
source link: https://blogread.cn/it/article/4167?f=hot1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
动态实时跟踪你的java程序 -- 系统运维 -- IT技术博客大学习 -- 共学习 共进步!
您现在的位置首页 --> 系统运维 --> 动态实时跟踪你的java程序

动态实时跟踪你的java程序

浏览:1707次  出处信息

    之前有写 基于AOP的日志调试 讨论一种跟踪Java程序的方法, 但不是很完美.后来发现了 Btrace , 由于它借助动态字节码注入技术 , 实现优雅且功能强大.

     只不过, 用起来总是磕磕绊绊的, 时常为了跟踪某个问题, 却花了大把的时间调试Btrace的脚本. 为此, 我尝试将几种跟踪模式固化成脚本模板, 待用的时候去调整一下正则表达式之类的.

     跟踪过程往往是假设与验证的螺旋迭代过程, 反复的用BTrace跟踪目标进程, 总有那么几次莫名其妙的不可用, 最后不得不重启目标进程. 若真是线上不能停的服务, 我想这种方式还是不靠谱啊.

     为此, 据决定自己的搞个用起来简单, 又能良好支持反复跟踪而不用重启目标进程的工具.

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

public void say(String words){
  Trace.enter();
  System.out.println(words);
  Trace.exit();
}

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

参数与返回值 当前实例状态

实现的选择

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

代理(装饰器)模式

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

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, 借助反射机制实现. 用它来是完成切面则会是:

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

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

pointcut say(): execute(* say(..))
before(): say() { ... }
after() : say() { ... }

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

CGlib

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

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进程进行动态的跟踪支持不了, 当然也许是我了解的不够深入, 若有基于上述方案的办法还请不吝赐教.

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

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

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

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

 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进行动态跟踪的管理.

后续的方向

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

The Java Interactive Profiler Proxy Javadoc Aspectj CGlib BTrace User’s Guide java动态跟踪分析工具BTrace实现原理 构建自己的监测工具 ASM Guide 常用 Java Profiling 工具的分析与比较 AOP@Work: Performance monitoring with AspectJ The JavaTM Virtual Machine Specification 来自rednaxelafx的JVM分享, 他的 Blog BCEL

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK