写点什么

JAVA AGENT 学习

用户头像
zane
关注
发布于: 2020 年 05 月 26 日

学习背景

在解决一个线上问题时发现是因为 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 方法。

发布于: 2020 年 05 月 26 日阅读数: 142
用户头像

zane

关注

还未添加个人签名 2019.12.09 加入

还未添加个人简介

评论

发布
暂无评论
JAVA AGENT 学习