Java Agent探针技术详解示例

yizhihongxing

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探针技术详解示例 - Python技术站

(0)
上一篇 2023年5月26日
下一篇 2023年5月26日

相关文章

  • PHP实现防盗链的方法分析

    PHP实现防盗链的方法分析 什么是防盗链? 防盗链是指在网页制作和浏览时,为防止他人在未经允许情况下盗用自己网站资源,也就是防止其他网站将本站的图片等媒体资源引用到自己的网站上。 PHP实现防盗链的方法 方法一:根据Referrer来判断 在HTTP请求头中,将发送来请求的页面地址和该页面上的链接按照上述格式传送给服务器,这个“发送来请求的页面地址”就是Re…

    Java 2023年6月15日
    00
  • Java常用工具类汇总 附示例代码

    Java常用工具类汇总 附示例代码 在Java编程中,我们常常要使用一些工具类来方便我们进行开发。本文将会汇总一些Java常用的工具类,旨在提供一个全面的工具类汇总供大家参考。我们将会介绍以下常用工具类: StringUtils:用于操作字符串的工具类。 DateUtils:用于时间和日期格式化、计算等操作的工具类。 MathUtils:用于数学计算的工具类…

    Java 2023年5月23日
    00
  • java calendar 日期实现不断加一天的代码

    此处提供两种计算 Java 日期的方法,可以达到不断加一天的效果。 方法一:使用 Calendar 类 Java 中可以使用 Calendar 类来操作日期,这个类提供了丰富的方法来计算日期、时间、星期等信息,而且使用 Calendar 类也很简单。下面给出示例代码: import java.util.Calendar; public class Calen…

    Java 2023年5月20日
    00
  • java 文件流的处理方式 文件打包成zip

    Java文件流的处理方式是 Java IO 提供的一种输入输出流 API。Java 的 IO 包提供了对外部数据来源和写入运行环境的能力,可以用于本地文件、网络资源、内存缓冲区等。Java IO 分为输入流和输出流两部分,其中输入流主要负责读取数据,而输出流则负责写入数据到指定位置。 Java 中可以使用java.util.zip和java.io包中提供的压…

    Java 2023年5月19日
    00
  • java经典问题:连个字符串互为回环变位

    标题:Java经典问题:连个字符串互为回环变位 问题描述 给定两个字符串,在不使用任何额外空间的情况下,判断这两个字符串是否互为回环变位。回环变位指的是将字符串中任意位置的字符剪切并粘贴到字符串末尾所得到的字符串。 例如,字符串 “abcde” 和 “cdeab” 就是互为回环变位的。 解决思路 对于给定的两个字符串 str1 和 str2,我们可以采取如下…

    Java 2023年5月27日
    00
  • Java SpringBoot自动装配原理详解及源码注释

    Java SpringBoot自动装配原理详解及源码注释是一篇关于SpringBoot自动装配原理的技术文章。文章介绍了SpringBoot如何实现自动装配,包括SpringBoot自动配置的流程和源代码注释。攻略包含以下内容: 1、什么是SpringBoot自动装配 首先,我们需要知道什么是SpringBoot自动装配。当我们使用SpringBoot框架时…

    Java 2023年5月19日
    00
  • 基于JAVA中的四种JSON解析方式详解

    基于Java中的四种JSON解析方式详解 JSON是一种轻量级的数据交换格式,在web开发中被广泛使用,同时Java中也提供了多种JSON解析方式。本篇文章将详细介绍Java中的四种JSON解析方式,并提供示例说明。 四种JSON解析方式 Java中提供的四种JSON解析方式包括: org.json:官方内置的JSON解析库 GSON:谷歌开源的JSON解析…

    Java 2023年5月26日
    00
  • mybatis实现获取入参是List和Map的取值

    对于MyBatis,我们可以通过Mapper接口的方法的入参类型来传递参数。如果我们需要传递List或者Map类型的参数,该如何处理呢?下面我们来一一讲解。 传递List类型的参数 当我们需要将一个List类型的参数传递给Mapper接口的方法时,我们可以采用@Param注解的方式将参数进行命名,如下所示: public interface UserMapp…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部