介绍
- JVM Tool Interface(JVMTI): JVM提供的一套API,用来供开发者通过代理的方式来监控和控制应用程序。
注意,JVMTI是通过代理的方式来实现,因此需要自己开发agent程序,另外,这里的agent程序是C/C++编写的程序编译之后生成的动态链接库,因此,为了方便java开发者开发agent程序,在Java SE5中新加入了Instrumentation机制,该机制底层仍是基于JVMTI,只不过以Java API的方式暴露出来,减小了开发的难度。
本文通过Instrumentation开发一个简单的agent,通过agent更改类的字节码的方式动态修改方法体,希望通过这个例子能让大家对Instrumentation有个直观的认识。
实践
1.定义简单类和主程序类
TransClass:
public class TransClass {
public int getNumber() {
return 10;
}
}
TestMain:
public class TestMain {
public static void main(String[] args) {
System.out.println(new TransClass().getNumber());
}
}
打成jar包agent-demo-main-1.0-SNAPSHOT.jar后运行:

可以看到返回结果为10.
2.编写agent
Java agent是以jar包形式依附在应用程序上,一般是通过java -javaagent:TestAgent.jar Main命令将agent挂载到主程序Main上。
2.1 agent入口类
package com.funstar;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println("执行premain");
inst.addTransformer(new Transformer());
}
}
premain函数是Java agent入口函数,参数String agentArgs通过java命令传入,Instrumentation inst是Instrumentation实例,由JVM自动传入,其接口定义如下:
public interface Instrumentation {
/**
* 注册一个Transformer,从此之后的类加载都会被Transformer拦截。
* Transformer可以直接对类的字节码byte[]进行修改
*/
void addTransformer(ClassFileTransformer transformer);
/**
* 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
* retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
*/
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
/**
* 获取一个对象的大小
*/
long getObjectSize(Object objectToSize);
/**
* 将一个jar加入到bootstrap classloader的 classpath里
*/
void appendToBootstrapClassLoaderSearch(JarFile jarfile);
/**
* 获取当前被JVM加载的所有类对象
*/
Class[] getAllLoadedClasses();
}
通过addTransformer添加ClassFileTransformer实例后,后续所有加载的类都会执行ClassFileTransformer.transform方法逻辑,因此可以在这个方法里对需要修改的类进行过滤拦截。
2.2 ClassFileTransformer实现类
public class Transformer implements ClassFileTransformer {
public static final String classNumberReturns2 = "MyTransClass.class";
public static byte[] getBytesFromFile(String fileName) {
try {
InputStream is = Transformer.class.getClassLoader().getResourceAsStream(fileName);
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while ((rc = is.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] byteArray = swapStream.toByteArray();
if (null == byteArray || byteArray.length == 0){
return null;
}
return byteArray;
} catch (Exception e) {
System.out.println("error occurs in _ClassTransformer!"
+ e.getClass().getName());
System.out.println(e.getMessage());
return null;
}
}
@Override
public byte[] transform(ClassLoader l, String className, Class<?> c,
ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
if (!className.contains("TransClass")) {
return null;
}
return getBytesFromFile(classNumberReturns2);
}
}
Transformer的逻辑是过滤类名为TransClass类,并通过MyTransClass.class文件内容替换这个类的定义。
2.3 修改TransClass逻辑,生成字节码文件
public class TransClass {
public TransClass() {
}
public int getNumber() {
return 1000;
}
}
编译这个文件后,重命名为MyTransClass.class,并放到classpath下,agent的目录结构如下:

2.4 生成agent jar包
打包生成的agent jar包的META-INF/MAINIFEST.MF文件中需要加入Premain-Class来指定agent入口类(有premain函数的类),可以通过maven-jar-plugin插件实现:
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.funstar.Premain</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
执行mvn package后包名为agent-demo-premain-1.0-SNAPSHOT.jar。
将agent挂载到原主程序重新执行

可以看到TransClass的方法体已经被修改,返回结果是1000.