赞
踩
Java Agent基于字节码增强技术研发,支持自动埋点完成数据上报,Java Agent包含(并二次分发)opentelemetry-java-instrumentation CNCF的开源代码,遵循Apache License 2.0协议,在Java Agent包中对opentelemetry License进行了引用。
OpenTelemetry是工具、API 和 SDK 的集合。使用它来检测、生成、收集和导出遥测数据(指标、日志和跟踪),以帮助您分析软件的性能和行为。OpenTelemetry社区活跃,技术更迭迅速,广泛兼容主流编程语言、组件与框架,为云原生微服务以及容器架构的链路追踪能力广受欢迎。通过对Java字节码的增强技术OpenTelemetry-java-instrumentation可以实现自动埋点上报数据。
Java Agent 直译为 Java 代理,中文圈也流行另外一个称呼 Java 探针 Probe 技术。
它在 JDK1.5 引入,是一种可以动态修改 Java 字节码的技术。Java 类编译后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码的信息,并且通过字节码转换器ClassFileTransformer 对这些字节码进行修改,以此来完成一些额外的功能。Java Agent 是一个不能独立运行 jar 包,它通过依附于目标程序的 JVM 进程,进行工作
//Java Agent 和目标进程一起启动模式
nohup java -jar -Dspring.profiles.active=prod -DworkId=1 -javaagent:/opentelemetry-javaagent.jar -Dfile.encoding=utf-8 -Xms4028M -Xmx4028M -XX:PermSize=512M -XX:MaxPermSize=512M /launch-adapter-0.0.1-SNAPSHOT.jar >/dev/null 2>&1 &
Agent 启动拦截提供两种方式:一种是程序运行前:在Main方法执行之前,通过一个叫 premain方法来执行
启动时需要在目标程序的启动参数中添加 -javaagent参数,Java Agent 内部通过注册 ClassFileTransformer ,这个转化器在Java 程序 Main方法前加了一层拦截器。在类加载之前,完成对字节码修改
• Java Agent 能够在加载 Java 字节码之前拦截并对字节码进行修改;
• Java Agent 能够在 Jvm 运行期间修改已经加载的字节码;
• IDE 的调试功能,例如 Eclipse、IntelliJ IDEA
• 热部署功能,例如 JRebel、XRebel、spring-loaded
• 各种线上诊断工具,例如 Btrace、Greys,国内阿里的 Arthas
• 各种性能分析工具,例如 Visual VM、JConsole 等
• 全链路性能检测工具,例如 OpenTelemetry、Skywalking、Pinpoint等 。
我们先实现一个简单的Java Agent的栗子
在编写类转化器时,我们通过Javassist 来具体操作字节码,首先pom.xml
里面添加依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency
premain方法
程序启动时,优先加载Java Agent,执行里面的 premain
方法。这个时候,其实大部分的类没有被加载。
- import java.lang.instrument.Instrumentation;
-
- public class MyAgent {
- /**
- * jvm 参数形式启动,运行此方法
- * @param agentArgs
- * @param inst
- */
- public static void premain(String agentArgs, Instrumentation inst){
- System.out.println("premain");
- customLogic(inst);
- }
-
- /**
- * 动态 attach 方式启动,运行此方法
- * @param agentArgs
- * @param inst
- */
- public static void agentmain(String agentArgs, Instrumentation inst){
- System.out.println("agentmain");
- customLogic(inst);
- }
-
- /**
- * 打印所有已加载的类名称
- * 修改字节码
- * @param inst
- */
- private static void customLogic(Instrumentation inst){
- inst.addTransformer(new MyTransformer(), true);
- Class[] classes = inst.getAllLoadedClasses();
- for(Class cls :classes){
- System.out.println(cls.getName());
- }
- }
- }
ClassFileTransformer提供了tranform()方法,用于对加载的类进行增强重定义,返回新的类字节码流。
Instrumentation 有一个TransformerInfo 数组保存ClassFileTransformer,像拦截器链表一样,顺序的进行字节码的重定义。
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtMethod;
-
- import java.io.ByteArrayInputStream;
- import java.lang.instrument.ClassFileTransformer;
- import java.lang.instrument.IllegalClassFormatException;
- import java.security.ProtectionDomain;
-
- public class MyTransformer implements ClassFileTransformer {
- @Override
- public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
- System.out.println("正在加载类:" + className);
-
- if (!className.contains("Person")) {
- return classfileBuffer;
- }
- CtClass cl = null;
- try {
- ClassPool classPool = ClassPool.getDefault();
- cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
- CtMethod ctMethod = cl.getDeclaredMethod("test");
- System.out.println("获取方法名称:" + ctMethod.getName());
- ctMethod.insertBefore("System.out.println(\" 动态插入的打印语句 \");");
- ctMethod.insertAfter("System.out.println($_);");
- byte[] transformed = cl.toBytecode();
- return transformed;
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- return classfileBuffer;
- }
- }
在 MANIFEST.MF
文件中定义Premain-Class
属性,指定一个实现类。类中实现了Premain方法,这就是Java Agent 在类加载启动入口
- Manifest-Version: 1.0
- Created-By: yyp
- Agent-Class: org.example.MyAgent
- Can-Redefine-Classes: true
- Can-Retransform-Classes: true
- Premain-Class: org.example.MyAgent
Premain-Class
包含Premain方法的类
Can-Redefine-Classes
为true时表示能够重新定义Class
Can-Retransform-Classes
为true时表示能够重新转换Class,实现字节码替换
POM打包
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <configuration>
- <archive>
- <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
- </archive>
- <descriptorRefs>
- <descriptorRef>jar-with-dependencies</descriptorRef>
- </descriptorRefs>
- </configuration>
- </plugin>
- </plugins>
- </build>
运行:
java -javaagent:MyCustomAgent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar myTest-1.0-SNAPSHOT.jar
- 正在加载类:java/util/regex/Pattern$Ques
- 正在加载类:java/util/regex/Pattern$Loop
- 正在加载类:java/util/regex/Pattern$Prolog
- 1
- 正在加载类:org/example/Person
- 获取方法名称:test
- 动态插入的打印语句
- 执行测试方法
- I'm ok
程序等价于:指定Java类下所有方法进行了如下转换,重新生成字节码加载执行
项目结构图
Java技术专题-Java Agent探针的技术介绍(1) - 简书
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。