参考: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方式来避免。