JAVA Intrumentation

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

 

本文会介绍 Java Instrumentation及其相关概念,会涉及到的名词包括:

 

  • Java Intrumentation API

  • Java agent

  • Attach API

  • JVMTI

  • ……

 

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

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

 

# Java Instrumentation

 

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

 

Instrumentation的一些主要方法如下:

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接口,定义如下:

/**
 * 传入参数表示一个即将被加载的类,包括了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。

 

# Java agent

 

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

 

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

 

1、Java agent 的格式

 

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

/**
 * 以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配置:

<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>

​​​​​​​

2、Java agent 的加载

 

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

 

启动时加载:通过vm的启动参数-javaagent:**.jar来启动

启动后加载:在vm启动后的任何时间点࿰

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值