Java Agent探针技术详解示例

Java Agent指的是一种能够以独立的模块形态运行的Java程序,它可以在应用程序运行期间在代码层面上监测应用程序的运行情况,记录应用程序运行过程中的各种参数和信息,这些信息对于分析系统性能、查找故障等都有着非常重要的意义。本文将

Java Agent指的是一种能够以独立的模块形态运行的Java程序,它可以在应用程序运行期间在代码层面上监测应用程序的运行情况,记录应用程序运行过程中的各种参数和信息,这些信息对于分析系统性能、查找故障等都有着非常重要的意义。本文将从以下两个方面详细讲解Java Agent探针技术的应用。

Java Agent探针技术的基本原理

Java Agent探针技术可以分为两个部分,一个是Agent程序,另一个是被监测的应用程序。Agent程序与应用程序分开运行,其实现方式是通过Java Virtual Machine(JVM)的-Instrumentation参数加载Agent Jar包,然后指定Agent程序。Agent程序与应用程序共享同一个JVM,它们之间通过Java API进行通信。当在应用程序中执行到需要被监测的代码时,Agent程序会打上探针标记,然后调用相应的逻辑进行具体监测与记录操作,最终输出监测数据。

示例

示例一:Java Agent实现应用性能监测

以监测方法耗时为例,首先需要在应用程序模块的Java代码中打印出方法执行开始和结束的时间戳:

long startTime = System.currentTimeMillis();
// 方法体
long endTime = System.currentTimeMillis();
System.out.println("Method executed in " + (endTime - startTime) + " ms");

然后,编写Java Agent程序,加载到应用程序的JVM中,并在其中通过Instrumentation类提供的API监听应用程序的方法执行情况,记录下每个方法的执行时间:

public class MyAgent {
    static Instrumentation is;

    public static void premain(String args, Instrumentation inst) {
        is = inst;
    }

    public static void agentmain(String args, Instrumentation inst) {
        is = inst;
    }

    public static void main(String[] args) {
        // 程序主逻辑
    }

    public static void methodExecuteTime(Class loadedClass, String methodName, long costTime) {
        System.out.println(loadedClass.getName() + "#" + methodName + " executed in " + costTime + " ms");
    }

    public static void premain(String args, Instrumentation inst) {
        System.out.println("MyAgent premain method invoked");
        inst.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                className = className.replace("/", ".");
                Class<?> cl = null;
                try {
                    cl = Class.forName(className);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                for (Method method : cl.getDeclaredMethods()) {
                    is.addTransformer(new Transformer(method.getName(), cl.getName()));
                }
                return classfileBuffer;
            }
        });

        try {
            is.retransformClasses(getLoadedClasses());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Transformer implements ClassFileTransformer {
    private String methodName;
    private String className;

    public Transformer(String methodName, String className) {
        this.methodName = methodName;
        this.className = className;
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
        MethodVisitor mv = new MyMethodVisitor(cv, methodName, className);
        cr.accept(mv, 0);
        return cw.toByteArray();
    }
}

class MyMethodVisitor extends AdviceAdapter {
    private int startTimeId = -1;
    private String methodName = null;
    private String className = null;

    public MyMethodVisitor(MethodVisitor mv, String methodName, String className) {
        super(ASM5, mv, Opcodes.ACC_PUBLIC, methodName, "()V");
        this.methodName = methodName;
        this.className = className;
    }

    protected void onMethodEnter() {
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        startTimeId = newLocal(Type.LONG_TYPE);
        mv.visitVarInsn(LSTORE, startTimeId);
    }

    protected void onMethodExit(int opcode) {
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LLOAD, startTimeId);
        mv.visitInsn(LSUB);
        mv.visitVarInsn(LSTORE, startTimeId);
        mv.visitLdcInsn(Type.getType("L" + className + ";"));
        mv.visitLdcInsn(methodName);
        mv.visitVarInsn(LLOAD, startTimeId);
        mv.visitMethodInsn(INVOKESTATIC, "com/github/hcsp/jaagent/MyAgent", "methodExecuteTime", "(Ljava/lang/Class;Ljava/lang/String;J)V", false);
    }
}

接下来,运行应用程序时增加Java Agent的启动参数,例如:

java -javaagent:/path/to/myagent.jar com.example.ApplicationMain

最后,查看应用程序的日志,即可看到每个方法的执行时间。

示例二:Java Agent实现代码追踪

以追踪方法调用流程为例,首先需要在应用程序模块的Java代码中打印出方法调用层次和参数信息:

StackTraceElement[] stackTraceList = Thread.currentThread().getStackTrace();
for (int i = 0; i < stackTraceList.length; i++) {
    StackTraceElement stackTrace = stackTraceList[i];
    System.out.println("at " + stackTrace.getClassName() + "#" + stackTrace.getMethodName() + 
        "(" + stackTrace.getFileName() + ":" + stackTrace.getLineNumber() + ")");
}

然后,编写Java Agent程序,加载到应用程序的JVM中,并在其中通过Instrumentation类提供的API监听应用程序的方法执行情况,记录下每个方法的调用信息:

public class MyAgent {
    static Instrumentation is;

    public static void premain(String args, Instrumentation inst) {
        is = inst;
    }

    public static void agentmain(String args, Instrumentation inst) {
        is = inst;
    }

    public static void main(String[] args) {
        // 程序主逻辑
    }

    public static void methodInvoke(String className, String methodName, Object... args) {
        System.out.println("invoke " + className + "#" + methodName + " method" + 
            (args != null && args.length > 0 ? ", params: " + Arrays.toString(args) : ""));
    }

    public static void premain(String args, Instrumentation inst) {
        System.out.println("MyAgent premain method invoked");
        inst.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                className = className.replace("/", ".");
                Class<?> cl = null;
                try {
                    cl = Class.forName(className);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                for (Method method : cl.getDeclaredMethods()) {
                    is.addTransformer(new Transformer(method.getName(), cl.getName()));
                }
                return classfileBuffer;
            }
        });

        try {
            is.retransformClasses(getLoadedClasses());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Transformer implements ClassFileTransformer {
    private String methodName;
    private String className;

    public Transformer(String methodName, String className) {
        this.methodName = methodName;
        this.className = className;
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
        MethodVisitor mv = new MyMethodVisitor(cv, methodName, className);
        cr.accept(mv, 0);
        return cw.toByteArray();
    }
}

class MyMethodVisitor extends AdviceAdapter {
    private String methodName = null;
    private String className = null;

    public MyMethodVisitor(MethodVisitor mv, String methodName, String className) {
        super(ASM5, mv, Opcodes.ACC_PUBLIC, methodName, "()V");
        this.methodName = methodName;
        this.className = className;
    }

    protected void onMethodEnter() {
        mv.visitLdcInsn(className);
        mv.visitLdcInsn(methodName);
        int argsCount = Type.getArgumentTypes(methodDesc).length;
        mv.visitIntInsn(BIPUSH, argsCount);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        for (int i = 0; i < argsCount; i++) {
            mv.visitInsn(DUP);
            mv.visitIntInsn(BIPUSH, i);
            mv.visitVarInsn(ALOAD, i + 1);
            mv.visitInsn(AASTORE);
        }
        mv.visitMethodInsn(INVOKESTATIC, "com/github/hcsp/jaagent/MyAgent", "methodInvoke",
                "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V", false);
    }
}

运行应用程序时增加Java Agent的启动参数,例如:

java -javaagent:/path/to/myagent.jar com.example.ApplicationMain

最后,查看应用程序的日志,即可看到方法调用流程和参数信息。

本文标题为:Java Agent探针技术详解示例

基础教程推荐