Java agent demo

参考:https://www.jianshu.com/p/efbc60dc530d

新建一个SpringBoot 工程,使其一直运行

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(DemoApplication.class, args);
        for(;;){
            Thread.sleep(1000);
            System.out.println(System.currentTimeMillis());
        }
    }

}

新建一个maven工程,打包为一个jar作为agent

public class PreMainTraceAgent {
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        System.out.println("agent begin agentArgs=" + agentArgs);
        Class[] allLoadedClasses = inst.getAllLoadedClasses();
        int i = 0;
        for (Class clazz : allLoadedClasses) {
            CodeSource cs = clazz.getProtectionDomain() == null ? null : clazz.getProtectionDomain().getCodeSource();
            String codeSource = "";
            if (null == cs || null == cs.getLocation() || null == cs.getLocation().getFile()) {
                codeSource = "";
            } else {
                codeSource = cs.getLocation().getFile();
            }

            if (i < 10) {
                System.out.println(clazz.getName());
                System.out.println(codeSource);
                System.out.println(clazz.getClassLoader());
            }
            i++;
        }
    }
}

pom: 加入manifestEntries

<?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>org.example</groupId>
    <artifactId>demo-agent</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestEntries>
                            <Premain-Class>com.xh.arthas.agent.PreMainTraceAgent</Premain-Class>
                            <Agent-Class>com.xh.arthas.agent.PreMainTraceAgent</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

mvn打包:

mvn clean assembly:assembly
[INFO] --- maven-assembly-plugin:2.2-beta-5:assembly (default-cli) @ demo-agent ---
[INFO] Building jar: F:codesarthasLearndemo-agent argetdemo-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

新建maven一个工程,作为attach入口:

public class Start {
    public static void main(String[] args) {
        System.out.println("running JVM start ");
        String jarUrl = "F:/codes/arthasLearn/demo-agent/target/demo-agent-1.0-SNAPSHOT-jar-with-dependencies.jar";
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        try {
            for (VirtualMachineDescriptor vmd : list) {
                if (vmd.displayName().contains("demo-0.0.1-SNAPSHOT")) {
                    VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                    virtualMachine.loadAgent(jarUrl,"**********************");
                    virtualMachine.detach();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后再SpringBoot工程中看到如下日志:

agent begin agentArgs=**********************
java.util.regex.Pattern$CharPredicate$$Lambda$394/0x0000000800e5d040

null
java.util.regex.CharPredicates$$Lambda$393/0x0000000800e5dc40

null
java.util.regex.Pattern$$Lambda$392/0x0000000800e5d840

null
java.util.regex.CharPredicates$$Lambda$391/0x0000000800e5e440

premain 和 agentmain 的区别
参考:https://zhuanlan.zhihu.com/p/96806300

Instrument Agent两种加载方式
在官方API文档[1]中提到,有两种获取Instrumentation接口实例的方法 :

JVM在指定代理的方式下启动,此时Instrumentation实例会传递到代理类的premain方法。
JVM提供一种在启动之后的某个时刻启动代理的机制,此时Instrumentation实例会传递到代理类代码的agentmain方法。
premain对应的就是VM启动时的Instrument Agent加载,即agent on load,agentmain对应的是VM运行时的Instrument Agent加载,
即agent on attach。两种加载形式所加载的Instrument Agent都关注同一个JVMTI事件 – ClassFileLoadHook事件,
这个事件是在读取字节码文件之后回调时用,也就是说premain和agentmain方式的回调时机都是类文件字节码读取之后(或者说是类加载之后),
之后对字节码进行重定义或重转换,不过修改的字节码也需要满足一些要求,在最后的局限性有说明。

premain与agentmain的区别:

premain和agentmain两种方式最终的目的都是为了回调Instrumentation实例并激活sun.instrument.InstrumentationImpl#transform()
(InstrumentationImpl是Instrumentation的实现类)从而回调注册到Instrumentation中的ClassFileTransformer实现字节码修改,本质功能上没有很大区别。

两者的非本质功能的区别如下:
premain方式是JDK1.5引入的,agentmain方式是JDK1.6引入的,JDK1.6之后可以自行选择使用premain或者agentmain。
premain需要通过命令行使用外部代理jar包,即-javaagent:代理jar包路径;agentmain则可以通过attach机制直接附着到目标VM中加载代理,
也就是使用agentmain方式下,操作attach的程序和被代理的程序可以是完全不同的两个程序。
premain方式回调到ClassFileTransformer中的类是虚拟机加载的所有类,这个是由于代理加载的顺序比较靠前决定的,在开发者逻辑看来就是:
所有类首次加载并且进入程序main()方法之前,premain方法会被激活,然后所有被加载的类都会执行ClassFileTransformer列表中的回调。
agentmain方式由于是采用attach机制,被代理的目标程序VM有可能很早之前已经启动,当然其所有类已经被加载完成,
这个时候需要借助Instrumentation#retransformClasses(Class<?>... classes)让对应的类可以重新转换,从而激活重新转换的类执行ClassFileTransformer列表中的回调。
通过premain方式的代理Jar包进行了更新的话,需要重启服务器,而agentmain方式的Jar包如果进行了更新的话,
需要重新attach,但是agentmain重新attach还会导致重复的字节码插入问题,不过也有Hotswap和DCE VM方式来避免。

原文地址:https://www.cnblogs.com/lanqie/p/14117108.html