当前位置:   article > 正文

Java Agent探针技术_java探针

java探针


前言

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 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 Agent 能够在加载 Java 字节码之前拦截并对字节码进行修改;

• Java Agent 能够在 Jvm 运行期间修改已经加载的字节码;

Java Agent 的价值

• IDE 的调试功能,例如 Eclipse、IntelliJ IDEA

• 热部署功能,例如 JRebel、XRebel、spring-loaded

• 各种线上诊断工具,例如 Btrace、Greys,国内阿里的 Arthas

• 各种性能分析工具,例如 Visual VM、JConsole 等

• 全链路性能检测工具,例如 OpenTelemetry、Skywalking、Pinpoint等 。

二、java Agent简单实现

我们先实现一个简单的Java Agent的栗子

引入javassist

在编写类转化器时,我们通过Javassist 来具体操作字节码,首先pom.xml 里面添加依赖

<dependency>

        <groupId>org.javassist</groupId>

        <artifactId>javassist</artifactId>

        <version>3.28.0-GA</version>

</dependency

实现premain方法

程序启动时,优先加载Java Agent,执行里面的 premain方法。这个时候,其实大部分的类没有被加载。 

  1. import java.lang.instrument.Instrumentation;
  2. public class MyAgent {
  3. /**
  4. * jvm 参数形式启动,运行此方法
  5. * @param agentArgs
  6. * @param inst
  7. */
  8. public static void premain(String agentArgs, Instrumentation inst){
  9. System.out.println("premain");
  10. customLogic(inst);
  11. }
  12. /**
  13. * 动态 attach 方式启动,运行此方法
  14. * @param agentArgs
  15. * @param inst
  16. */
  17. public static void agentmain(String agentArgs, Instrumentation inst){
  18. System.out.println("agentmain");
  19. customLogic(inst);
  20. }
  21. /**
  22. * 打印所有已加载的类名称
  23. * 修改字节码
  24. * @param inst
  25. */
  26. private static void customLogic(Instrumentation inst){
  27. inst.addTransformer(new MyTransformer(), true);
  28. Class[] classes = inst.getAllLoadedClasses();
  29. for(Class cls :classes){
  30. System.out.println(cls.getName());
  31. }
  32. }
  33. }

实现ClassFileTransformer 

ClassFileTransformer提供了tranform()方法,用于对加载的类进行增强重定义,返回新的类字节码流。

Instrumentation 有一个TransformerInfo 数组保存ClassFileTransformer,像拦截器链表一样,顺序的进行字节码的重定义。

  1. import javassist.ClassPool;
  2. import javassist.CtClass;
  3. import javassist.CtMethod;
  4. import java.io.ByteArrayInputStream;
  5. import java.lang.instrument.ClassFileTransformer;
  6. import java.lang.instrument.IllegalClassFormatException;
  7. import java.security.ProtectionDomain;
  8. public class MyTransformer implements ClassFileTransformer {
  9. @Override
  10. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  11. System.out.println("正在加载类:" + className);
  12. if (!className.contains("Person")) {
  13. return classfileBuffer;
  14. }
  15. CtClass cl = null;
  16. try {
  17. ClassPool classPool = ClassPool.getDefault();
  18. cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
  19. CtMethod ctMethod = cl.getDeclaredMethod("test");
  20. System.out.println("获取方法名称:" + ctMethod.getName());
  21. ctMethod.insertBefore("System.out.println(\" 动态插入的打印语句 \");");
  22. ctMethod.insertAfter("System.out.println($_);");
  23. byte[] transformed = cl.toBytecode();
  24. return transformed;
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. return classfileBuffer;
  29. }
  30. }

MANIFEST.MF文件配置 

在 MANIFEST.MF文件中定义Premain-Class属性,指定一个实现类。类中实现了Premain方法,这就是Java Agent 在类加载启动入口

  1. Manifest-Version: 1.0
  2. Created-By: yyp
  3. Agent-Class: org.example.MyAgent
  4. Can-Redefine-Classes: true
  5. Can-Retransform-Classes: true
  6. Premain-Class: org.example.MyAgent
  • Premain-Class包含Premain方法的类

  • Can-Redefine-Classes为true时表示能够重新定义Class

  • Can-Retransform-Classes为true时表示能够重新转换Class,实现字节码替换

打包和运行 

POM打包

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.apache.maven.plugins</groupId>
  5. <artifactId>maven-assembly-plugin</artifactId>
  6. <configuration>
  7. <archive>
  8. <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
  9. </archive>
  10. <descriptorRefs>
  11. <descriptorRef>jar-with-dependencies</descriptorRef>
  12. </descriptorRefs>
  13. </configuration>
  14. </plugin>
  15. </plugins>
  16. </build>

运行:

java -javaagent:MyCustomAgent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar myTest-1.0-SNAPSHOT.jar 

  1. 正在加载类:java/util/regex/Pattern$Ques
  2. 正在加载类:java/util/regex/Pattern$Loop
  3. 正在加载类:java/util/regex/Pattern$Prolog
  4. 1
  5. 正在加载类:org/example/Person
  6. 获取方法名称:test
  7. 动态插入的打印语句
  8. 执行测试方法
  9. I'm ok

程序等价于:指定Java类下所有方法进行了如下转换,重新生成字节码加载执行 

项目结构图 


参考

Java技术专题-Java Agent探针的技术介绍(1) - 简书

深入Java自动化探针技术的原理和实践_java 探针原理-CSDN博客

Java 动态调试技术原理及实践 - 美团技术团队 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/438859
推荐阅读
相关标签
  

闽ICP备14008679号