JAVA探针机制—Agent(一)

JAVA探针机制—Agent(一)

agent机制首次出现在JDK5版本,在JDK6版本得到升级并且正式被官方定义为agent原理。

首先要明确JavaAgent是一个JVM层面的插件,他可以利用JDK中的Instrumenttation类,实现对类字节码文件的修改。

而Agent在功能上的实现有两种情况:

  1. 在main方法执行前,调用premain方法。
  2. 在main方法执行后,监控JVM虚拟机的同时,调用agentmain方法。

目前市场上很多日志、热部署、性能监控等等插件功能,都是基于探针中agentmain方法实现的,那么本文就详细的说明下如何自定义去实现一个Agent,Java探针插件功能。

插件

作为一个插件,他是需要我们提前去配置好一个属性的。

Premain-Class:指定代理类
Agent-Class:指定代理类
Boot-Class-PathL:指定bootstrap类加载器的路径
Can-Redefine-Classes:是否重新定义所有类
Can-Retransform-Classes:是否需要retransform函数

假如项目是一个Maven项目,则需要在Maven中注入maven-jar的插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>                                								xyz.leyuna.laboratory.core.agent.AgentScene
                            </Premain-Class>
                            <Can-Redefine-Classes>
                                true
                            </Can-Redefine-Classes>
                            <Can-Retransform-Classes>
                                true
                            </Can-Retransform-Classes>
                            <Manifest-Version>
                                true
                            </Manifest-Version>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

除此之外还有更多的参数,以及参数的意义,有需要扩展的可以直接看JDK的英文文档:

https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html?is-external=true

插件使用:

再Vm参数设置一行

image-20220526004726718.png
通过 -javaagent:路径/agent的jar包名=你需要设置的入参agentOps

代码实现

premain

premain方法是在main方法执行前会进行的方法,常用与日志、某些功能的起步加载等等。

首先要明确,premain方法一定得是premain名,不可改变,他是JDK设置agent机制时的一个规范,所以我们在代码中由两种实现premain方法的模板:

1、

    public static void premain(String agentOps, Instrumentation inst){
        System.out.println("方法一");
        System.out.println(agentOps);
    }

2、

    public static void premain(String agentOps){
        System.out.println("方法二");
        System.out.println(agentOps);
    }

两个方法的执行优先度是:1>2,当1存在且正常执行后,不会再执行2.

首先说下参数agentOps,是再VM设置加载插件时,进入的入参。

Instrumentation,agent机制的核心类,使用该类,可以对类字节、原class等直接进行操作。

那么这个方法的一个运行测试图下:
image-20220526005030543.png

image-20220526005046486.png

agentmain

agentmain是在agent插件中涉及的最多的方法,他是一个可在main方法运行后,再次进行的方法。那么JVM的性能监控、热部署等等功能,都是基于在main方法运行后,可再次进行的特性完成,并且因为Instrumentation提供可直接操作类字节码的特性,我们可以做到对指定class文件的实时替换或修改。

agentmain的实现:

    public static void agentmain(String agentOps, Instrumentation inst) {
        System.out.println("main方法之后执行");
        String className = "指定替换的类名";
        Class[] allLoadedClasses = inst.getAllLoadedClasses();
        Class tempClazz = null;
        for (Class clazz : allLoadedClasses) {
            if (className.equals(clazz.getCanonicalName())) {
                //需要替换类的类字节码文件
                tempClazz = clazz;
            }
        }
        ClassDefinition classDefinition = new 	  
        ClassDefinition(tempClazz,"替换的类字节码的二进制数".getBytes());
        //替换
        inst.redefineClasses(classDefinition);
    }

除了定义agentmain方法外,还需要在main方法中自己开启agentmain方法的注入。

    public static void main(String[] args)  {
        //如果本虚拟机线程id为1,则监听他
        VirtualMachine attach = VirtualMachine.attach("1");
        //注入agentmain的探针系统
        attach.loadAgent("探针jar包的路径", "ops参数");
    }

这里有一点要注意VirtualMachine不是JDK自动注入的类,他在com.sun.tools包下。

所以如果你的项目中目前没有这个jar包的依赖则需要在JDK的目录下lib/tools.jar,自己手动导入此jar包。

总结

以上就是自己手动定义出一个agent探针程序的实现了,接下来的一篇,会具体分析:

  1. 如何做到的
  2. 能做到什么
  3. 怎么做
### Java Agent 使用方法 Java Agent 提供了种强大的机制,在不改变原有代码的情况下,允许开发者在应用程序启动前或运行期间动态修改字节码。这使得性能监控、日志记录和其他横切关注点的处理变得更加容易。 #### 应用启动时使用 `premain` 方法 当希望在应用启动之前执行某些逻辑时,可以利用 `premain` 方法作为切入点。此方法会在 JVM 初始化之后但在任何用户级别的线程创建之前被执行[^1]。下面是个简单的例子展示了如何定义并实现这样个代理: ```java public class MyAgent { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("MyAgent is running before the application starts."); // Register a transformer to modify bytecode at load time. inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(Module module, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // Transformation logic here... return classfileBuffer; } }); } } ``` 为了使上述代码生效,还需要配置 MANIFEST.MF 文件以声明该 JAR个合法的 Java Agent: ``` Manifest-Version: 1.0 Premain-Class: com.example.MyAgent Can-Redefine-Classes: true Can-Retransform-Classes: true ``` #### 动态附加到正在运行的应用程序上 除了静态地设置 `-javaagent` 参数外,还可以通过 Attach API 来动态连接至已存在的 JVM 实例,并安装新的 instrumentation 探针。这种方式非常适合于生产环境中部署诊断工具而不必重启服务[^2]。 以下是使用 Attach API 的基本流程: 1. 获取目标进程 ID; 2. 创建与之关联的 VirtualMachine 对象; 3. 调用其 `loadAgent()` 或者 `loadAgentPath()` 方法传入相应的 jar 包路径以及可选参数字符串; ```java VirtualMachine vm = VirtualMachine.attach(targetPid); vm.loadAgent("/path/to/javaagent.jar", "optional arguments"); vm.detach(); ``` 需要注意的是,这种方法依赖于 sun.jvmstat 和 tools.jar 这两个库的存在,因此可能不适合所有环境下的跨平台移植需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值