动态实时跟踪你的java程序
来源:互联网 发布:javascript对象构造器 编辑:程序博客网 时间:2024/05/16 19:56
之前有写 基于AOP的日志调试 讨论一种跟踪Java程序的方法, 但不是很完美.后来发现了 Btrace , 由于它借助动态字节码注入技术 , 实现优雅且功能强大.
只不过, 用起来总是磕磕绊绊的, 时常为了跟踪某个问题, 却花了大把的时间调试Btrace的脚本. 为此, 我尝试将几种跟踪模式固化成脚本模板, 待用的时候去调整一下正则表达式之类的.
跟踪过程往往是假设与验证的螺旋迭代过程, 反复的用BTrace跟踪目标进程, 总有那么几次莫名其妙的不可用, 最后不得不重启目标进程. 若真是线上不能停的服务, 我想这种方式还是不靠谱啊.
为此, 据决定自己的搞个用起来简单, 又能良好支持反复跟踪而不用重启目标进程的工具.
AOP
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
- 动态实时跟踪你的java程序
- 动态实时跟踪你的java程序
- 多目标实时跟踪程序
- 关于java程序的跟踪调试
- 用文本文件实现的动态实时发布新闻的程序
- 用文本文件实现的动态实时发布新闻的程序
- 动态跟踪Java代码的执行状况工具--BTrace
- 动态跟踪Java代码的执行状况工具--BTrace
- RTX实时平台介绍(1) - 让你的Win32程序享受到实时控制的好处
- 实时跟踪log变化的工具Apachetop
- 动态跟踪程序运行状态一法
- 使用oracle的系统跟踪功能调试java程序
- 要素动态跟踪的算法
- java 面试题: 使用监听来实现实时跟踪
- java摄像头实时摄像程序
- 程序的测试与跟踪
- log4j2 的 xml 配置与程序的调用以解决 java 程序的日志和跟踪
- 实时跟踪文件内容
- Socket函数说明
- POJ-3050-Hopscotch
- 在C#中解决动态计算表达式的问题(如字符串"Sin(1)+Cos(2)",执行并得出结果)
- 常用的英语缩写含义
- OJ_1077 最大序列和
- 动态实时跟踪你的java程序
- android repo中manifest.xml的详解
- java学习笔记——基础知识
- 机房综合布线资源管理系统功能介绍
- 定时任务轮询(spring Task)
- 使用异步 I/O 大大提高应用程序的性能
- Android SIM多次热插拔,某次插入,不识别SIM卡
- C#读写文件总结
- 一个反射将对象转化为sql语句的实例