学习背景
在解决一个线上问题时发现是因为 JAVA 线程池本身的设计导致,要彻底解决的话需要重写这部分实现。
然后就找了一些资料研究怎么重写 JAVA 底层的类,就发现了 javaagent。
主要学习资料如下:
JVM TI
JavaAgent 原理与实践
JVM 源码分析之 javaagent 原理完全解读
简介
>启动时加载的 JavaAgent 是 JDK1.5 之后引入的新特性,此特性为用户提供了在 JVM 将字节码文件读入内存之后,JVM 使用对应的字节流在 Java 堆中生成一个 Class 对象之前,用户可以对其字节码进行修改的能力,从而 JVM 也将会使用用户修改过之后的字节码进行 Class 对象的创建。
是什么
介绍 javaagent 之前也要介绍另一个概念 JVMTI。
JVMTI 是 JDK 提供的一套用于开发 JVM 监控, 问题定位与性能调优工具的通用编程接口(API)。
通过 JVM TI,我们可以开发各式各样的 JVMTI Agent。这个 Agent 的表现形式是一个以 C/C++语言编写的动态共享库。
javaagent 可以帮助我们快速使用 JVMTI 的功能,又不需要重写编写 C/C++的底层库。
javaagent 是依赖 java 底层提供的一个叫 instrument 的 JVMTI Agent。这个 agent 又叫 JPLISAgent(Java Programming Language Instrumentation Services Agent)
简单来说,javaagent 是一个 JVM 的“插件”。
在 java 运行命令中 javaagent 是一个参数,用来指定 agent。
能做什么
可以在加载 class 文件之前进行拦截并把字节码做修改。
可以在运行期对已加载类的字节码做变更,但是这种情况下会有很多的限制。
还有其他一些小众的功能
获取所有已经加载过的类
获取所有已经初始化过的类(执行过 clinit 方法,是上面的一个子集)
获取某个对象的大小
将某个 jar 加入到 bootstrap classpath 里作为高优先级被 bootstrapClassloader 加载
将某个 jar 加入到 classpath 里供 AppClassloard 去加载
设置某些 native 方法的前缀,主要在查找 native 方法的时候做规则匹配
总的来说可以让 JVM 按照我们的预期逻辑去执行。
最主要的也是使用最广的功能就是对字节码的修改。通过对字节码的修改我们就可以实现对 JAVA 底层源码的重写,也正好可以满足我之前的需求。
我们还可以做:
完全非侵入式的进行代码埋点,进行系统监控
修改 JAVA 底层源码,进行 JVM 自定义
实现 AOP 动态代理
Javaagent 案例
接下可以创建一个全新的 Maven 项目,编写 Javaagent 的案例。
项目需要添加两个 maven 依赖。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.23.2-GA</version>
<optional>true</optional>
</dependency>
复制代码
定义代理类
定义我们的第一代理类 FirstAgent。这个代理类只做一个事情,在 Instrumentation 添加了一个我们自定义的 Transformer。
public class FirstAgent {
public static void premain(String agentArgs, Instrumentation inst){
System.out.println("FirstAgent is Start.");
inst.addTransformer(new FirstTransformer());
}
}
复制代码
实现 ClassFileTransformer 接口
接下来我们自定义一个 ClassFileTransformer 接口的实现
在这个类里,我们对原有的类做了两点修改:
增加了一个 sex 属性
重写 toString 方法
在修改字节码时使用了 javassist 这个字节码工具。
Javaassist 就是一个用来处理 Java 字节码的类库,有机会再详细介绍。
User
public class User {
public String name;
public User(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
复制代码
FirstTransformer
public class FirstTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//只修改自定义的User类
if(className.equals("User")){
try {
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(new LoaderClassPath(loader));
CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer), false);
//定义一个String类型的sex属性
CtField param = new CtField(classPool.get("java.lang.String"), "sex", clazz);
//设置属性为private
param.setModifiers(Modifier.PRIVATE);
//将属性加到类中,并设置属性的默认值为male
clazz.addField(param, CtField.Initializer.constant("male"));
//为刚才的sex属性添加GET SET 方法
clazz.addMethod(CtNewMethod.setter("setSex", param));
clazz.addMethod(CtNewMethod.getter("getSex", param));
//重写toString方法,将sex属性加入返回结果中。
CtMethod method = clazz.getDeclaredMethod("toString");
method.setBody("return \"User{\" +\n" +
" \"name='\" + name + '\\',' +\n" +
" \"sex='\" + sex + '\\'' +\n" +
" '}';");
return clazz.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
复制代码
打包
FirstAgent 加上 FirstTransformer 就完成了第一个 javaagent 的全部代码编写部分。接下来进行打包,生成第一个 javaagent
打包可以直接使用 maven 的打包工具,用 mvn install 打包即可。
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>FirstAgent</Premain-Class>
<Boot-Class-Path>firstAgent.jar</Boot-Class-Path>
<Can-Redefine-Classes>false</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>false</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
复制代码
这个里面有几个重要的配置:
Premain-Class :包含 premain 方法的类(类的全路径名)我们这里就是 FirstAgent 类
Boot-Class-Path :设置打包 jar 的文件名
Can-Redefine-Classes :true 表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix:true 表示能设置此代理所需的本机方法前缀,默认值为 false(可选)
测试 javaagent
编写一个测试方法
public static void main(String[] args) {
User user = new User("zane");
System.out.println(JSON.toJSON(user));
System.out.println(user.toString());
}
复制代码
在没有使用我们编写的 javaagent 时,输出如下:
{"name":"zane"}
User{name='zane'}
复制代码
在运用时添加参数 -javaagent:/Users/zane/javaagent/target/javaagent-1.0.jar,使用我们的代理类,注意路径修改成你本地的 jar 包地址。
在运行输出如下:
FirstAgent is Start.
{"sex":"male","name":"zane"}
User{name='zane,sex='male'}
复制代码
可以看到代理已经生效, user 对象新增了一个 sex 的属性,并重写了 toString 方法。
评论