JavaAgent黑盒子揭秘

简介

java.lang.instrument 包是Java 中来增强JVM 上的应用的一种方式,机制是在JVM 启动前或启动后attach 上去进行修改方法字节码的方式。
instrument 包的用途很多,主要体现在对代码侵入低的优点上。
例如一些监控不方便修改业务代码,但是可以使用这种方式在方法中植入特定逻辑,这种方式能够直接修改JVM 中加载的字节码的内容,而不需要在像Spring AOP 实现中创建新的代理类,所以在底层侵入更高,但是对开发者更透明。

使用方式是通过在启动命令上添加参数的方式添加一个称为agent 的jar 包。

1
java -javaagent:xxx.jar xx.jar

用途

  • 可以在加载 class 文件之前做拦截,对字节码做修改。
  • 可以在运行期对已加载类的字节码做变更,但是这种情况下会有很多的限制。
  • 还有其他一些小众的功能:
    • 获取所有已经加载过的类。
    • 获取所有已经初始化过的类(执行过 clinit 方法,是上面的一个子集)。
    • 获取某个对象的大小。
    • 将某个 jar 加入到 bootstrap classpath 里作为高优先级被 bootstrapClassloader 加载。
    • 将某个 jar 加入到 classpath 里供 AppClassloard 去加载。
    • 设置某些 native 方法的前缀,主要在查找 native 方法的时候做规则匹配。

示例:

  • 用于自动添加getter/setter方法的工具lombok就使用了这一技术。
  • 另外btrace 和housemd 等动态诊断工具也是用了instrument技术。
  • 在实现不停机的情况下更新jar 包,即热部署,也是使用了这一技术。

简单实现

创建agent jar 包必须具备的两个特点:

  • 创建META-INF/MANEFIST.MD文件,并设置Premain-Class。
  • 在创建的类中,实现premain(String agentOps, Instrumentation inst) 函数。

MANIFEST.MF

1
2
3
Manifest-Version: 1.0
Premain-Class: cn.vgbhfive.GreetingAgent
Can-Redefine-Classes: true

实现类

GreetingAgent 类:

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
30
31
32
33
package cn.vgbhfive;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Date;

/**
* @auther Vgbh
*/
public class GreetingAgent {

public static void premain(String options, Instrumentation inst) {
if (options != null) {
System.out.println("be called " + options);
} else {
System.out.println("no called " + options);
}
inst.addTransformer((ClassFileTransformer) new GreetingTransformer1());
}

static class GreetingTransformer1 implements ClassFileTransformer{
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("jvmTest/a".equals(className)) {
System.out.println("Dog's method invoke at " + new Date());
}
return null;
}
}
}

测试

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.vgbhfive;

/**
* @auther Vgbh
*/
public class a {

public static void main(String[] args) {
System.out.println(new b().hello());
}

static class b {
public String hello() {
return "Hello World!";
}
}
}

结果

1
// TODO

实现热部署

Spring 官方的热部署组件就是基于Java Agent 实现的,有兴趣的可以看看。
但是目前还有几个小问题亟待解决。
https://github.com/spring-projects/spring-loaded


引用

https://liuzhengyang.github.io/2017/03/15/javaagent/
https://www.cnkirito.moe/instrument/


个人备注

此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!