添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  1. 1 Overview
  2. 2 Java Instrumentation
  3. 3 Java agent
    1. 3.1 Java agent 的格式
    2. 3.2 Java agent 的加载
    3. 3.3 举个例子
  4. 4 Attach API
    1. 4.1 Attach API 用法
    2. 4.2 Attach API 原理
  5. 5 JVM Tool Interface(JVMTI)
  6. 6 相关技术的实际应用
    1. 6.1 btrace等诊断工具
      1. 6.1.1 btrace
      2. 6.1.2 Greys
    2. 6.2 热部署
      1. 6.2.1 IDE提供的HotSwap
      2. 6.2.2 Tomcat的自动reload
      3. 6.2.3 JRebel,spring-loaded,hotcode2等热部署工具
      4. 6.2.4 Dynamic Code Evolution VM (DCE VM)
  7. 7 参考资料

1 Overview

对于Java 程序员来说,Java Instrumentation、Java agent这些技术可能平时接触的很少,听上去陌生但又好像在哪里见到过。实际上,我们日常应用的各种工具中,有很多都是基于他们实现的,例如常见的热部署(JRebel, spring-loaded)、各种线上诊断工具(btrace, Greys)、代码覆盖率工具(JaCoCo)等等。
本文会介绍 Java Instrumentation及其相关概念,会涉及到的名词包括:

  • Java Instrumentation API
  • Java agent
  • Attach API
  • JVMTI
  • 简单的来看,如果需要通过Instrumentation操作或监控一个Java程序,相关的工具和流程如下:

    下文会依次介绍图中的相关概念,并谈谈原理和具体的应用场景。

    2 Java Instrumentation

    Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向classLoader的classpath下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。
    Instrumentation的一些主要方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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 transformer)了,这个方法可以在类加载时做拦截,对输入的类的字节码进行修改,其参数是一个ClassFileTransformer接口,定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 传入参数表示一个即将被加载的类,包括了classloader,classname和字节码byte[]
    * 返回值为需要被修改后的字节码byte[]
    */
    byte[]
    transform( ClassLoader loader,
    String className,
    Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain,
    byte[] classfileBuffer) throws IllegalClassFormatException;

    addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。

    主流的JVM都提供了Instrumentation的实现,但是鉴于Instrumentation的特殊功能,并不适合直接提供在JDK的runtime里,而更适合出现在Java程序的外层,以上帝视角在合适的时机出现。因此如果想使用Instrumentation功能, 拿到Instrumentation实例,我们必须通过Java agent

    3 Java agent

    Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。

    Java agent与Instrumentation密不可分,二者也需要在一起使用。因为Instrumentation的实例会作为参数注入到Java agent的启动方法中。

    3.1 Java agent 的格式

    Java agent以jar包的形式部署在JVM中,jar文件的manifest需要指定agent的类名。根据不同的启动时机,agent类需要实现不同的方法(二选一)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    /**
    * 以vm参数的形式载入,在程序main方法执行之前执行
    * 其jar包的manifest需要配置属性Premain-Class
    */
    public static void premain(String agentArgs, Instrumentation inst);

    /**
    * 以Attach的方式载入,在Java程序启动后执行
    * 其jar包的manifest需要配置属性Agent-Class
    */
    public static void agentmain(String agentArgs, Instrumentation inst);

    因此,如果想自己写一个java agent程序,只需定义一个包含premain或者agentmain的类,在方法中实现你的逻辑,然后在打包jar时配置一下manifest即可。可以参考如下的maven plugin配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
    <archive>
    <manifestEntries>
    <Premain-Class>**.**.InstrumentTest</Premain-Class>
    <Agent-Class>**.**..InstrumentTest</Agent-Class>
    <Can-Redefine-Classes>true</Can-Redefine-Classes>
    <Can-Retransform-Classes>true</Can-Retransform-Classes>
    </manifestEntries>
    </archive>
    </configuration>
    </plugin>

    3.2 Java agent 的加载

    一个Java agent既可以在VM启动时加载,也可以在VM启动后加载:

  • 启动时加载:通过vm的启动参数-javaagent:**.jar来启动
  • 启动后加载:在vm启动后的任何时间点,通过attach api,动态地启动agent
  •