字节码插桩简析
我们都知道java文件需要编译成class字节码文件才能被jvm加载执行,这里我们顺便说一下jvm加载一个class类的流程,如图:
验证:即是验证class文件内容的有效性,可能你会说都是java的编译器javac干的咋还可能会有错呢,这就是和我们今天的主角有关系了
准备:对类的静态变量赋初值,这里不包括final修饰的静态变量,因为它是在编译阶段就赋值了,另外final修饰的静态变量引用不会引起类加载,同时它的赋初值并不是你在类里面给的值,例如 static int i = 2,在准备阶段赋的值是0
解析:将常量池内的符合引用转化为直接引用,不理解可以不必深究,了解即可
初始化:对类的所有变量进行初始化,同时将上面准备阶段已经初始化过的静态变量赋所需要的值,另外类的初始化会生成一个类对象
使用:这个我不会
卸载:类的卸载条件比较苛刻,需要class对象及其classLoader对象(即加载这个class类的类加载器)同时释放才能对其进行垃圾回收,所以在java8之前加载的class对象会被放在称为永久代的方法区中,从这里看来类对象也不是越多越好,所以开发的时候就别一个字段就搞一个类了
介绍了这么多,我们现在进入正题,首先我们先来说一下刚开始的一个问题,既然class是jdk编译的,那么为啥还需要检查呢,这是因为编译之后的class文件是可以被修改的,在java中要修改class需要两种技术,分别是javaAgent和javassist,第一个负责在class被classLoader加载之前对classLoader进行拦截,第二个是负责将class的二进制文件进行修改,具体怎么修改的细节不用关注,怕你会了公司请不起你:
说完了javaAgent的运行流程,我们再来说说在哪些场景需要用到它,第一个主要场景是在面试中,可以反问面试官知不知道agent就敢来面试你,第二个主要场景是在写服务链路监控相关的,也可以简单点理解为埋点,但是这种方式是动态织入的,意思就是说不需要在业务代码中加入这些埋点逻辑,你可以通过javaagent的方式去attah你的业务服务,这种方式的好处是对业务无侵入,是一种边车设计模式,能够实现基础设施下沉,降低微服务的复杂度。
最后我们来简单说一下javaAgent如何使用:
1、编写agent方法
public class MyAgent {
public static void premain(String args, Instrumentation instrumentation) throws Exception {
System.out.println("Hello javaagent permain:"+args);
}
}
2、添加premain-class参数
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Premain-Class>com.javaagent.MyAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<skip>true</skip>
</configuration>
</plugin>
3、构建打包
4、在任一JAVA应用中 添加jvm 参数并启动 -javaagent:xxx.jarjavaagent META-INF/MANIFEST.MF
参数说明:
Premain-Class:必填,agent启动
classCan-Redefine-Classes:默认为false ,是否允许重新定义
classCan-Retransform-Classes:默认为false,是否允许重置Class,重置后相当于class 从classLoade中清除,下次有需要的时候会重新装载,也会重新走Transformer 流程。
Boot-Class-Path:agent 所依赖的jar 路径,多个用空格分割
创建一个测试类MyAgentTest并运行查看结果
public class MyAgentTest {
public static void main(String[] args) {
System.out.println("main");
}
}
//运行结果:main
添加jvm参数
参数内容:-javaagent:/Users/jinyunlong/IdeaProjects/test-agent/target/test-agent-1.0-SNAPSHOT.jar=123
再次运行测试类MyAgentTest并查看结果
总结:
优点:javaAgent其实本质上就是改变class文件,更准确的应该说是动态改变class中的方法,对其进行增强,比如说统一埋点,打日志或者其它业务功能,能够让这些基础功能和业务解耦,实现基础设施下沉,同时还能保证较高的准确度
缺点:部署比较麻烦,其次通过这种方式对类的方法进行增强,或多或少会影响服务的性能,这要根据你的业务流量来判断是否需要接入