乐趣区

关于java: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");
退出移动版