• Java-基于 Instrument 的 Agent


    Agent 为 JVMTI 的客户端。

    这里记录的是基于Java Instrument 的 Agent 实现,还有直接基于 JVMTI 的 Agent 实现

    在 JDK1.5 以后,我们可以使用 Agent 技术构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他 JVM 上的程序。使用它可以实现虚拟机级别的 AOP 功能。

    Agent 分为两种,一种是在主程序之前运行的 Agent,一种是在主程序之后运行的 Agent(JDK1.6 以后)。

    一、在主程序运行之前的代理程序

    1.编写 agent 程序

    package before;
    
    import java.lang.instrument.Instrumentation;
    
    public class AgentApplication {
        public static void premain(String arg, Instrumentation instrumentation) {
            System.err.println("agent startup , args is " + arg);
        }
    }

    2.添加 MANIFEST.MF 文件

    路径为 META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Premain-Class: before.AgentApplication
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true

    若使用的是 Maven 编译就不用手动添加,配置 pom.xml 即可

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>before.AgentApplication</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

    3.运行

    新建一个测试类并编译为 class 文件

    package com;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("123");
        }
    }

    使用命令运行

    java -javaagent:..mahout-1.0-SNAPSHOT.jar=abc com.Main

    可以看到 premain 方法在 main 之前运行了

    二、在主程序运行之后的代理程序

    关于动态 attach:https://openjdk.java.net/groups/hotspot/docs/Serviceability.html#battach

    https://juejin.im/post/5b0d020d518825153f10403f

    1.编写 agent 程序

    由于是在主程序运行后再执行,意味着我们可以获取主程序运行时的信息,这里我们打印出来主程序中加载的类名。

    package after;
    
    import java.lang.instrument.Instrumentation;
    
    public class AgentApplication {
        public static void agentmain(String arg, Instrumentation instrumentation) {
            System.err.println("agent startup , args is " + arg);
    
            Class<?>[] classes = instrumentation.getAllLoadedClasses();
            for (Class<?> cls : classes) {
                System.out.println(cls.getName());
            }
        }
    }

    2.添加 MANIFEST.MF 文件

    路径为 META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Agent-Class: after.AgentApplication
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true

    若使用的是 Maven 编译就不用手动添加,配置 pom.xml 即可

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Agent-Class>after.AgentApplication</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

    3.运行

    由于是在程序运行之后运行,需要先有一个 Java 进程,直接用 IDE 运行即可,不用使用命令行。

    package com;
    
    public class Main {
        public static void main(String[] args) throws InterruptedException {
            for (; ; ) {
                System.out.println("123");
                Thread.sleep(1000);
            }
        }
    }

    运行后查看改程序的进程号,这里为 9084

    然后将 agent 程序附加到上面程序的进程中

    com.sun.tools.attach.VirtualMachine 在 JAVA_HOME 路径下 lib/tools.jar 中,如果 IDE 报找不到,可以手动将 tools.jar 添加进来,或配置 CLASSPATH 环境变量。

    package com;
    
    import com.sun.tools.attach.AgentInitializationException;
    import com.sun.tools.attach.AgentLoadException;
    import com.sun.tools.attach.AttachNotSupportedException;
    import com.sun.tools.attach.VirtualMachine;
    
    import java.io.IOException;
    
    public class Attach {
        public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
            VirtualMachine vm = VirtualMachine.attach("9084");
            vm.loadAgent("D:\IDEA\CodeLib\jhxxb\mahout\target\mahout-1.0-SNAPSHOT.jar");
        }
    }

    直接 IDE 运行即可,执行完毕后回到 Main 程序的打印控制台

    三、使用相关

    其中 -javaagent 可以有多个,但如果把 -javaagent 放在 -jar 后面,则不会生效。也就是放在主程序后面的 agent 是无效的。

    如:java -javaagent:D:myagent-1.jar=ABC -javaagent:D:myagent-2.jar=DEF -jar myapp.jar -javaagent:D:myagent-3.jar=GHI,其中 myagent-3.jar 是无效的。

    其中 premain(agentmain) 方法有两个:

    1. public static void premain(String agentArgs, Instrumentation inst)
    2. public static void premain(String agentArgs)

    JVM 会优先加载 1,加载成功则忽略 2,如没有 1,则加载 2。加载逻辑在 sun.instrument.InstrumentationImpl 类中:

    private void loadClassAndStartAgent(String classname, String methodname, String optionsString) throws Throwable {
        ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader();
        Class<?> javaAgentClass = mainAppLoader.loadClass(classname);
    
        Method m = null;
        NoSuchMethodException firstExc = null;
        boolean twoArgAgent = false;
    
        // The agent class must have a premain or agentmain method that
        // has 1 or 2 arguments. We check in the following order:
        //
        // 1) declared with a signature of (String, Instrumentation)
        // 2) declared with a signature of (String)
        // 3) inherited with a signature of (String, Instrumentation)
        // 4) inherited with a signature of (String)
        //
        // So the declared version of either 1-arg or 2-arg always takes
        // primary precedence over an inherited version. After that, the
        // 2-arg version takes precedence over the 1-arg version.
        //
        // If no method is found then we throw the NoSuchMethodException
        // from the first attempt so that the exception text indicates
        // the lookup failed for the 2-arg method (same as JDK5.0).
    
        try {
            m = javaAgentClass.getDeclaredMethod(methodname, new Class<?>[]{String.class, java.lang.instrument.Instrumentation.class});
            twoArgAgent = true;
        } catch (NoSuchMethodException x) {
            // remember the NoSuchMethodException
            firstExc = x;
        }
    
        if (m == null) {
            // now try the declared 1-arg method
            try {
                m = javaAgentClass.getDeclaredMethod(methodname, new Class<?>[]{String.class});
            } catch (NoSuchMethodException x) {
                // ignore this exception because we'll try
                // two arg inheritance next
            }
        }
    
        if (m == null) {
            // now try the inherited 2-arg method
            try {
                m = javaAgentClass.getMethod(methodname, new Class<?>[]{String.class, java.lang.instrument.Instrumentation.class});
                twoArgAgent = true;
            } catch (NoSuchMethodException x) {
                // ignore this exception because we'll try
                // one arg inheritance next
            }
        }
    
        if (m == null) {
            // finally try the inherited 1-arg method
            try {
                m = javaAgentClass.getMethod(methodname, new Class<?>[]{String.class});
            } catch (NoSuchMethodException x) {
                // none of the methods exists so we throw the
                // first NoSuchMethodException as per 5.0
                throw firstExc;
            }
        }
    
        // the premain method should not be required to be public,
        // make it accessible so we can call it
        // Note: The spec says the following:
        //     The agent class must implement a public static premain method...
        setAccessible(m, true);
    
        // invoke the 1 or 2-arg method
        if (twoArgAgent) {
            m.invoke(null, new Object[]{optionsString, this});
        } else {
            m.invoke(null, new Object[]{optionsString});
        }
    
        // don't let others access a non-public premain method
        setAccessible(m, false);
    }
    View Code

    Instrument premain、agentmain 方法执行时机:

    premain 执行时机:在 JVM 启动时(所有的 Java 类都未被初始化,所有的对象实例都未被创建),初始化函数 eventHandlerVMinit 会调用 sun.instrument.instrumentationImpl 类的 loadClassAndCallPremain 方法去执行 Premain-Class 指定类的 premain 方法。

    agentmain 执行时机:在 JVM 启动后,通过 VirtualMachine 附着一个 Instrument,如:vm.loadAgent(jar),会调用 sun.instrument.instrumentationImpl 类的 loadClassAndCallAgentmain 方法去执行 Agentmain-Class 指定类的 agentmain 方法。

    premain、agentmain 方法中两个参数:

    agentArgs:代理程序命令行中输入参数,随同 “-javaagent” 一起传入,与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组。

    inst:java.lang.instrument.Instrumentation 实例,由 JVM 自动传入,集中了几乎所有功能方法,如:类操作、classpath 操作等。

    META-INF/MAINFEST.MF 参数:

    • Premain-Class:指定包含 premain 方法的类名。
    • Agent-Class:指定包含 agentmain 方法的类名。
    • Boot-Class-Path:指定引导类加载器搜索的路径列表。查找类的特点于平台的机制失败后,引导类加载器会搜索这些路径。
    • Can-Redefine-Class:是否能重新定义此代理所需的类,默认为 false。
    • Can-Retransform-Class:是否能重新转换此代理所需的类,默认为 false。
    • Can-Set-Native-Method-Prefix:是否能设置此代理所需的本机方法前缀,默认值为 false。

    https://www.jianshu.com/p/63c328ca208d

    https://yq.aliyun.com/articles/658806

    https://www.jianshu.com/p/9f4e8dcb3e2f

    https://juejin.im/post/5b0925ec51882538aa1ee248

    https://www.ibm.com/developerworks/cn/java/j-lo-jpda2

  • 相关阅读:
    网站的内容安全策略(CSP)
    javascript学习日记--eval、prompt
    【java每日一学】Applet类详解
    JavaScript 闭包应用-打印所有li元素的内容
    JavaScript 闭包应用-计算打车价格
    JavaScript 闭包应用-点击li输出索引号
    JavaScript jQuery 任务清单 ToDoList
    JavaScript 面向对象TAB栏切换
    JavaScript 常见移动端网页特效
    JavaScript 移动端轮播图
  • 原文地址:https://www.cnblogs.com/jhxxb/p/11567337.html
Copyright © 2020-2023  润新知