字节码插桩简析

共 3162字,需浏览 7分钟

 ·

2023-05-10 22:46

        我们都知道java文件需要编译成class字节码文件才能被jvm加载执行,这里我们顺便说一下jvm加载一个class类的流程,如图:

7845df2c6346023ef11b4b2a0ee5edef.webp


验证:即是验证class文件内容的有效性,可能你会说都是java的编译器javac干的咋还可能会有错呢,这就是和我们今天的主角有关系了

2cd7c8a90c97c00b9956a07fdf3cbb56.webp


准备:对类的静态变量赋初值,这里不包括final修饰的静态变量,因为它是在编译阶段就赋值了,另外final修饰的静态变量引用不会引起类加载,同时它的赋初值并不是你在类里面给的值,例如 static int i = 2,在准备阶段赋的值是0


解析:将常量池内的符合引用转化为直接引用,不理解可以不必深究,了解即可


初始化:对类的所有变量进行初始化,同时将上面准备阶段已经初始化过的静态变量赋所需要的值,另外类的初始化会生成一个类对象


使用:这个我不会


卸载:类的卸载条件比较苛刻,需要class对象及其classLoader对象(即加载这个class类的类加载器)同时释放才能对其进行垃圾回收,所以在java8之前加载的class对象会被放在称为永久代的方法区中,从这里看来类对象也不是越多越好,所以开发的时候就别一个字段就搞一个类了


       介绍了这么多,我们现在进入正题,首先我们先来说一下刚开始的一个问题,既然class是jdk编译的,那么为啥还需要检查呢,这是因为编译之后的class文件是可以被修改的,在java中要修改class需要两种技术,分别是javaAgent和javassist,第一个负责在class被classLoader加载之前对classLoader进行拦截,第二个是负责将class的二进制文件进行修改,具体怎么修改的细节不用关注,怕你会了公司请不起你:2f3f2db61f3c03ece1812b6f4d82f403.webp


         说完了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参数

1c05f3490bd99d57fd35f77e566bf0a5.webp

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

1c05f3490bd99d57fd35f77e566bf0a5.webp

3、构建打包

7eac400c22ed5db5c2524db703a5501b.webp

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

1e637e22a7b452fd8e83ab28223f9657.webp


再次运行测试类MyAgentTest并查看结果

9282b9238ddd45e5253b7e41988cdb6e.webp


总结

优点:javaAgent其实本质上就是改变class文件,更准确的应该说是动态改变class中的方法,对其进行增强,比如说统一埋点,打日志或者其它业务功能,能够让这些基础功能和业务解耦,实现基础设施下沉,同时还能保证较高的准确度

缺点:部署比较麻烦,其次通过这种方式对类的方法进行增强,或多或少会影响服务的性能,这要根据你的业务流量来判断是否需要接入


浏览 43
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报