Java Instrument

JVMTI

什麼是JVMTI

JVM Tool Interface簡稱JVMTI是一組對外接口,通過這組接口可以實現,獲取虛擬機運行狀態、線程分析、監控、調試、覆蓋率分析等功能。

JVMTIAgent

什麼是JVMTIAgent

爲了使用JVMTI提供的對外接口,一般採用Agent方式來實現JVMTI提供的對外接口,JVMTIAgent類似於c語言的動態庫的概念。

實現方式

Java1.5之前實現一個Agent只能通過原生的c/c++來實現Agent,在Java1.5之後提供了instrumentagent,也叫做JPLISAgent(Java Programming Language Instrumentation Services Agent)專門用於Java方式。

啓動方式

Agent有兩種啓動方式

  • 第一種是在jvm啓動的時候,指定agent程序的位置來啓動。
  • 另外一種方式是jvm已經在運行了,使用attach的方式到目標進程裏面。在java1.5的時候只支持jvm啓動,在java1.6的時候支持attach的方式啓動,在jvmtool.jar裏面提供了工具VirtualMachine來幫助啓動agent

Instrument

什麼是Instrument

Instrument提供了爲Java編程語言插入代碼的服務,Instrumentation是在方法中添加字節碼,以便收集使用的數據,由於這些改變是添加字節碼,不會修改程序的狀態或者行爲。比如監視器代碼、探查器、覆蓋率分析器和事件記錄器。

Instrument只是提供插入代碼服務,在方法中添加字節碼,至於具體的字節碼操作,是由字節碼操作工具來實現的,常見的字節碼操作工具包括:CGLIBJavassistASM等。

獲取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");
      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章