战损版JavaAgent方法耗时统计工具实现

前言

本篇文章将实现一个超绝战损版的基于Java Agent的方法耗时统计工具。

整体内容分为:

  1. Java Agent原理简析;
  2. 方法耗时统计工具实现;
  3. 方法耗时工具的Springbootstarter包实现。

正文

一. Java Agent原理简析

理解啥是Java Agent前,需要先介绍一下JVM TIJVM Tool Interface)。

JVM TIJVM提供的用于访问JVM各种状态的一套编程接口。基于JVM TI可以注册各种JVM事件钩子函数,当JVM事件发生时,触发钩子函数以对相应的JVM事件进行处理。

那么Java Agent可以理解为就是JVM TI的一种具体实现。关于Java Agent,可以概括其特性如下。

  1. 是一个jar包;
  2. 无法独立运行;
  3. JDK1.5)可以在程序运行前被加载,加载后会调用到Java Agent提供的入口函数premain(String agentArgs, Instrumentation inst)
  4. JDK1.6开始)可以在程序运行中被加载,加载后会调用到Java Agent提供的入口函数agentmain(String agentArgs, Instrumentation inst)

如果想要agentmain() 方法被调用,则需要将Agent程序attach到主进程的JVM上,这时就需要使用到com.sun.tools.attach包里提供的Attach APIAgentattachJVM后,agentagentmain() 方法就会被调用。

最后说明一下Java Agent的入口函数中的类型为Instrumentation的参数。InstrumentJVM提供的一套能够对Java代码进行插桩操作的服务能力,JDK1.5Instrument支持在JVM启动并加载类时修改类,InstrumentJDK1.6开始支持在程序运行时修改类。Instrument提供的重要方法如下所示。

public interface Instrumentation {

    // ...

    /**
     * JDK1.5提供,注册一个{@link ClassFileTransformer}。
     * 等同于addTransformer(transformer, false)。
     *
     * @param transformer {@link ClassFileTransformer}。
     */
    void addTransformer(ClassFileTransformer transformer);

    /**
     * JDK1.6提供,注册一个{@link ClassFileTransformer}。
     *
     * @param transformer {@link ClassFileTransformer}。
     * @param canRetransform false表示注册的{@link ClassFileTransformer}仅对首次加载的类生效,
     *                       即首次加载类时可以改变这个类的定义再完成加载;
     *                       true表示注册的{@link ClassFileTransformer}可对已加载的类生效,即
     *                       可对已加载的类进行重定义并重加载,重加载重定义的类时会覆盖已加载的类。
     */
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

    /**
     * JDK1.6提供,重定义并重加载传入的类。
     *
     * @param classes 需要重定义并重加载的类。
     * @throws UnmodifiableClassException 传入的类无法被修改时抛出。
     */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

    // ...

}
复制代码

也就是可以向Instrumentation注册ClassFileTransformer

JDK1.5时只能通过addTransformer(ClassFileTransformer) 方法注册ClassFileTransformer,此时每个类被加载到JVM中之前会调用到注册的ClassFileTransformertransform() 方法,并可以在其中先改变类定义后再将类加载到JVM中。

JDK1.6开始提供了addTransformer(ClassFileTransformer, boolean) 方法,当第二个参数传入false时,效果与addTransformer(ClassFileTransformer) 方法一样,当第二个参数传入true时,那么此时注册的ClassFileTransformer除了在类被加载到JVM中之前会调用到,还会在retransformClasses(Class<?>... classes) 方法调用时被调用到,也就是此时注册的ClassFileTransformer支持对通过retransformClasses(Class<?>... classes) 方法传入的类进行重定义然后再重加载到JVM中。

二. 整体构思

首先,因为是超绝战损版,所以我们的方法耗时统计,伪代码可以表示如下。

public class TestDemo {

    public void execute() {
        // 记录开始时间
        long beginTime = System.currentTimeMillis();

        // 原方法方法体
	// ...

        // 记录结束时间
        long endTime = System.currentTimeMillis();
        // 记录执行耗时
        long executeTime = endTime - beginTime;
        // 超绝战损版打印
        System.out.println(executeTime);
    }

}
复制代码

其次,我们需要编写一个Java Agent,且希望能够在程序运行时加载这个Java Agent,所以编写的Java Agent需要提供入口函数agentmain(String agentArgs, Instrumentation inst),此时Java Agent需要通过com.sun.tools.attach包里提供的Attach API来加载并附加到主进程JVM上。

然后,在Java Agent中,我们需要初始化ClassFileTransformer,然后将ClassFileTransformer注册到Instrumentation,再然后获取到需要重定义的类并通过InstrumentationretransformClasses(Class<?>... classes) 方法将这些类传递到注册的ClassFileTransformer中。

接着,在我们自定义的ClassFileTransformer中,需要借助Javassist的能力,为相应的类添加方法耗时统计的代码片段,并完成重加载。

最后,还需要编写一个测试程序来验证我们的超绝战损版方法耗时打印工具的功能。

整体的一个流程示意图如下。

三. 方法耗时统计工具实现

现在开始代码实现。首先创建一个Maven工程,命名为myagent-corePOM文件如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lee.learn.agent</groupId>
    <artifactId>myagent-core</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
宿主应用不能直接调用Java Agent中的静态方法,因为Java Agent是在独立的Java虚拟机中运行的,与宿主应用是独立的进程。Java Agent和宿主应用之间是通过Java Instrumentation API进行通信的。 在Java Agent中,可以通过premain方法获取到Instrumentation实例,并保存在静态变量中。然后,在宿主应用中,可以通过调用Java Agent提供的公共接口来获取Java Agent中的静态变量或执行Java Agent中的方法。 以下是一个示例: Java Agent中的公共接口: ``` public class MyAgent { private static Instrumentation instrumentation; public static void premain(String args, Instrumentation inst) { instrumentation = inst; } public static Object getObjectThreadLocal(Thread thread, ThreadLocal threadLocal) { return instrumentation.getObjectThreadLocal(thread, threadLocal); } // 其他公共方法 } ``` 宿主应用中的代码: ``` import java.lang.instrument.Instrumentation; public class MyApp { public static void main(String[] args) { // 加载Java Agent String agentJarPath = "/path/to/agent.jar"; String agentArgs = "arg1,arg2"; Instrumentation instrumentation = loadAgent(agentJarPath, agentArgs); // 调用Java Agent中的公共接口 Thread targetThread = // 获取目标线程 ThreadLocal threadLocal = // 获取目标线程中的ThreadLocal对象 Object threadLocalValue = MyAgent.getObjectThreadLocal(targetThread, threadLocal); // 其他业务逻辑 } private static Instrumentation loadAgent(String agentJarPath, String agentArgs) { try { // 通过VirtualMachine.loadAgent方法加载Java Agent VirtualMachine vm = VirtualMachine.attach("pid"); vm.loadAgent(agentJarPath, agentArgs); return MyAgent.getInstrumentation(); } catch (Exception e) { e.printStackTrace(); return null; } } } ``` 在这个示例中,宿主应用通过VirtualMachine.loadAgent方法加载Java Agent,并调用Java Agent提供的公共接口MyAgent.getObjectThreadLocal来获取ThreadLocal变量。需要注意的是,加载Java Agent需要使用VirtualMachine.attach方法,该方法需要传递Java虚拟机的进程ID(pid)。可以通过jps命令来获取Java虚拟机的进程ID。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值