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

  • 【Java Instrument】第二种方式是在pom文件中指定,本质上也是在配置MANIFEST.MF文件
    com.lee.agent.PreMainAgent com.lee.agent.PreMainAgent

指定接收方法
  • 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);

示例代码 整体流程图示 Java Instrument
文章图片

目标程序 目标程序是被操作的程序,被修改的是目标类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");

    推荐阅读