JVMTI
什麼是JVMTI
JVM Tool Interface
簡稱JVMTI
是一組對外接口,通過這組接口可以實現,獲取虛擬機運行狀態、線程分析、監控、調試、覆蓋率分析等功能。
JVMTIAgent
什麼是JVMTIAgent
爲了使用JVMTI
提供的對外接口,一般採用Agent
方式來實現JVMTI
提供的對外接口,JVMTIAgent
類似於c
語言的動態庫的概念。
實現方式
在Java1.5
之前實現一個Agent
只能通過原生的c/c++
來實現Agent
,在Java1.5
之後提供了instrument
的agent
,也叫做JPLISAgent(Java Programming Language Instrumentation Services Agent)
專門用於Java
方式。
啓動方式
Agent
有兩種啓動方式
- 第一種是在
jvm
啓動的時候,指定agent
程序的位置來啓動。 - 另外一種方式是jvm已經在運行了,使用
attach
的方式到目標進程裏面。在java1.5
的時候只支持jvm
啓動,在java1.6
的時候支持attach
的方式啓動,在jvm
的tool.jar
裏面提供了工具VirtualMachine
來幫助啓動agent
。
Instrument
什麼是Instrument
Instrument
提供了爲Java
編程語言插入代碼的服務,Instrumentation
是在方法中添加字節碼,以便收集使用的數據,由於這些改變是添加字節碼,不會修改程序的狀態或者行爲。比如監視器代碼、探查器、覆蓋率分析器和事件記錄器。
Instrument
只是提供插入代碼服務,在方法中添加字節碼,至於具體的字節碼操作,是由字節碼操作工具來實現的,常見的字節碼操作工具包括:CGLIB
、Javassist
、ASM
等。
獲取Instrumentation實例
指定接收類
要獲取Instrumentation
實例,首先要指定將Instrumentation
實例傳遞給哪個類,有兩種方式來指定傳遞給這個類。
-
第一種方式是在配置文件
resource\META_INF\MANIFEST.MF
中指定。Manifest-Version: 1.0 Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: com.lee.agent.PreMainAgent Agent-Class: com.lee.agent.PreMainAgent
-
第二種方式是在
pom
文件中指定,本質上也是在配置MANIFEST.MF
文件<plugin> <excutions> <excution> <archive> <manifestFile> <Premain-Class>com.lee.agent.PreMainAgent</Premain-Class> <Agent-Class>com.lee.agent.PreMainAgent</Agent-Class> </manifestFile> </archive> </excution> </excutions> </plugin>
指定接收方法
-
當
JVM
以指定代理類的方式啓動,在這種情況下Instrumentation
實例被傳給代理類的premain
方法;public static void premain(String agentArgs, Instrumentation inst); public static void premain(String agentArgs);
-
當
JVM
啓動後,以attach
的方式指定代理類,在這種情況下Instrumentation
實例被傳遞給代理類的agentmain
方法。public static void agentmain(String agentArgs, Instrumentation inst); public static void agentmain(String agentArgs);
示例代碼
整體流程圖示
目標程序
目標程序是被操作的程序,被修改的是目標類TargetClass
。
public class Demo {
public static void main(String[] args) throws Exception {
TargetClass targetClass = new TargetClass();
targetClass.targetMethod();
}
}
public class TargetClass {
public String targetMethod() {
System.out.println("執行測試方式");
return "return";
}
}
Agent程序
public class PreMainAgent {
/**
* 指定agentjar包啓動,Instrument實例會傳遞給這個方法
*/
public static void premain(String agentArgs, Instrumentation inst){
customLogic(inst);
}
/**
* attach方法啓動,Instrument實例會傳遞給這個方法
*/
public static void agentmain(String agentArgs, Instrumentation inst){
customLogic(inst);
}
private static void customLogic(Instrumentation inst){
inst.addTransformer(new MyClassTransformer(), true);
}
}
class MyClassTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
final ClassPool classPool = ClassPool.getDefault();
CtClass clazz;
CtMethod ctMethod;
try {
if ("com/lee/TargetClass".equals(className)){
clazz = classPool.get("com.lee.TargetClass");
ctMethod = clazz.getDeclaredMethod("targetMethod");
ctMethod.insertBefore("System.out.println(\"****************\");");
byte[] byteCode = clazz.toBytecode();
clazz.detach();
return byteCode;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
啓動
-
先將
agent
項目打包成一個jar
包,agent.jar
-
兩種啓動方式
-
在啓動目標程序的時候指定
agent
的位置:-javaagent:jar包路徑\Jagent.jar
-
以
attach
方式啓動// project1啓動的pid VirtualMachine vm = VirtualMachine.attach("1856"); vm.loadAgent("jar包路徑\Jagent.jar");
-