文章采集调用(使用Java的Instrumentation接口(java.lang.instrument)(组图) )

优采云 发布时间: 2022-01-16 20:24

  文章采集调用(使用Java的Instrumentation接口(java.lang.instrument)(组图)

)

  前言

  在分布式链路跟踪中,为了获取服务之间的调用链信息,采集器通常需要在方法前后进行埋藏。在Java生态系统中,常见的埋点方式有两种:

  依靠SDK手动埋点;使用Java Agent技术做非侵入式埋葬。

  著名的分布式监控系统是由 Zipkin 开创的。最经典的就是了解X-B3 Ttrace协议,使用Brave SDK,手动埋点生成Trace。但是 SDK 中埋点的方式对业务代码是有侵入性的。升级埋点时,必须进行代码更改。

  那么如何将其与业务逻辑解绑呢?

  Java还提供了另一种方式:依靠Java Agent技术对目标方法的字节码进行修改,从而实现非侵入式埋葬。这种使用 Java 代理的 采集器 方式也称为探针。在应用启动时使用-javaagent参数,或者在运行时使用attach(pid)方法,可以将探针包注入到目标应用中,完成埋点的植入。以对业务代码无侵入的方式,实现不敏感的热升级。用户无需了解深层原理,即可使用完整的监控服务

  Java代理介绍

  Java Agent 是 Java 1.5 版本之后引入的一个特性。它的主要作用是在类加载之前对其进行拦截,并且已经插入到我们的监控字节码中。代理是使用 Java 的 Instrumentation 接口 (java.lang.instrument) 编写的。

  基本思路是在JVM启动时添加一个代理(Java Agent),每个代理是一个Jar包,在MANIFEST.MF文件中指定代理类,代理类收录一个premain方法。JVM在类加载的时候会先执行代理类的premain方法,然后再执行Java程序本身的main方法,这就是premain名的来源。在premain方法中,可以修改加载前的类文件。

  这种机制可以认为是虚拟机级别的AOP,无需对原创应用程序进行任何修改即可实现类的动态修改和增强。

  从 JDK 1.6 开始,支持更强大的动态 Instruments,在 JVM 启动后通过 Attach(pid) 远程加载。

  注意:

  无论 Agent 是用 Native 方式还是 Java Instrumentation 接口方式编写的,它们的工作都是在 JVMTI 的帮助下完成的。 JVMTI 是一组 Native 接口。在 Java 1.5 之前,Agent 只能通过编写 Native 代码来实现。

  Java Instrumentation 核心方法

  Instrumentation 是 java.lang.instrument 包下的一个接口。该接口的方法提供了注册类文件转换器、获取所有加载的类等功能,允许我们修改加载和卸载的类来实现AOP、性能监控等功能。

  常用方法:

  /**

* 为 Instrumentation 注册一个类文件转换器,可以修改读取类文件字节码

*/

void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

/**

* 对JVM已经加载的类重新触发类加载

*/

void retransformClasses(Class... classes) throws UnmodifiableClassException;

/**

* 获取当前 JVM 加载的所有类对象

*/

Class[] getAllLoadedClasses()

  它的 addTransformer 为 Instrumentation 注册了一个转换器。Transformer 是 ClassFileTransformer 接口的一个实例。这个接口只有一个转换方法。调用addTransformer设置transformer后,后续JVM会在加载所有类之前被这个transform方法拦截。此方法接收原创类文件。字节数组,返回转换后的字节数组,可以在这个方法中做任何类文件的重写。

  public class MyClassTransformer implements ClassFileTransformer {

@Override

public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException {

// 在这里读取、转换类文件

return classBytes;

}

}

  Java Agent 核心流程

  Java Agent加载时序图(premain):

  

  类加载时序图:

  

  Java 代理使用的 Instrumentation 取决于 JVMTI 实现。当然也可以绕过Instrumentation,直接使用JVMTI来实现Agent。JVMTI 和 JDI 构成了 Java 平台调试架构 (JPDA) 的主要功能。

  Java 代理使用

  Java Agent 实际上是一个特殊的 Jar 包。它不能单独启动,而必须附加到 JVM 进程。可以看成是JVM的寄生插件。它使用 Instrumentation API 来读取和重写当前 JVM 的类。文件,并通过 -javaagent:xxx.jar 导入目标应用程序。

  那么这个Jar和普通Jar有什么区别呢?

  

  Agent需要打包成jar包,在Maininfe.MF属性中指定“Premain-Class”或“Agent-Class”,根据需求定义Can-Redefine-Classes和Can-Retransform-Classes。

  Java Agent Jar包MANIFEST.MF配置参数:

  Manifest-Version: 1.0

#动态 agent 类

Agent-Class: com.zuozewei.javaagent01.Agent

#静态 agent 类

Premain-Class: com.zuozewei.javaagent01.Agent

是否允许重复装载

Can-Redefine-Classes: true

Can-Retransform-Classes: true

Created-By: Apache Maven 3.3.9

Build-Jdk: 1.8.0_112

  演示预览

  1、创建POM项目Java Agent,项目结构如下:

  

  2、修改pom文件:

  

parent

com.zuozewei

1.0-SNAPSHOT

4.0.0

jar

javaagent01

agent

org.apache.maven.plugins

maven-jar-plugin

2.3.1

src/main/resources/META-INF/MANIFEST.MF

maven-assembly-plugin

${basedir}

true

true

com.zuozewei.javaagent01.Agent

jar-with-dependencies

org.apache.maven.plugins

maven-compiler-plugin

1.7

1.7

  3、创建一个AgentMain类实现控制台打印,添加Transformer为Instrumentation注册一个transformer。

  package com.zuozewei.javaagent01;

import java.lang.instrument.Instrumentation;

public class Agent {

// public static void premain(String agentArgs) {

// System.out.println("我是一个萌萌哒的 Java Agent");

// try {

// Thread.sleep(2000L);

// } catch (InterruptedException e) {

// e.printStackTrace();

// }

// }

public static void premain(String agentArgs, Instrumentation instrumentation) {

instrumentation.addTransformer(new ClassFileTransformerDemo());

System.out.println("7DGroup Java Agent");

}

}

  4、创建一个ClassFileTransformerDemo类,截取并打印所有类名。

  package com.zuozewei.javaagent01;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

public class ClassFileTransformerDemo implements ClassFileTransformer {

public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {

System.out.println("className: " + className);

if (!className.equalsIgnoreCase("com/zuozewei/Dog")) {

return null;

}

return getBytesFromFile("/Users/zuozewei/IdeaProjects/javaagent/example01/target/classes/com/zuozewei/Dog.class");

}

public static byte[] getBytesFromFile(String fileName) {

File file = new File(fileName);

try (InputStream is = new FileInputStream(file)) {

// precondition

long length = file.length();

byte[] bytes = new byte[(int) length];

// Read in the bytes

int offset = 0;

int numRead = 0;

while (offset = 0) {

offset += numRead;

}

if (offset < bytes.length) {

throw new IOException("Could not completely read file "

+ file.getName());

}

is.close();

return bytes;

} catch (Exception e) {

System.out.println("error occurs in _ClassTransformer!"

+ e.getClass().getName());

return null;

}

}

}

  5、定义需要修改的项目example01

  6、需要修改的类的实现。

  主要的:

  package com.zuozewei;

public class Main {

public static void main(String[] args) {

System.out.println("7DGroup");

System.out.println(new Dog().hello());

// System.out.println(new Cat().hello());

}

}

  狗:

  package com.zuozewei;

public class Dog {

public int hello() {

return 0;

}

}

  7、运行example01的main方法:

  

  8、将javaagent项目打包生成jar文件,将java文件与上图example01项目的jar放在同一目录下(方便执行放在同一目录下)

  执行以下命令:

  java -jar -javaagent:agent.jar example.jar

  我们的函数实现了,执行结果如下:

  

  总结

  本文详细介绍了Java Agent启动加载实现字节码增强关键技术的实现细节。字节码增强技术为测试人员监控性能提供了新思路。目前很多开源监控产品都提供了丰富的Java探针库。作为监控服务商,开发成本进一步降低,但开发门槛较高,对测试人员的学习成本有很大的影响。

  源地址:

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线