字节码增强技术之 Java Agent 入门

简介: 本文详细介绍 Java Agent 启动加载实现字节码增强关键技术的实现细节,字节码增强技术为测试人员进行性能监控提供了一种新的思路。目前众多开源监控产品已经提供了丰富的 Java 探针库,作为监控服务的提供者,进一步降低了开发成本,不过开发门槛比较高,对测试人员来说有很大的一部分的学习成本。

前言

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

  1. 依赖 SDK 手动埋点;
  2. 利用 Java Agent 技术来做无侵入埋点。

我们所熟知的分布式监控系统,是 Zipkin 开始的,最经典的是搞懂 X-B3 Ttrace 协议,使用 Brave SDK,手动埋点生成 Trace。但是 SDK 埋点的方式,对业务代码存在侵入性,当升级埋点时,必须要做代码的变更。

那么如何和业务逻辑解绑呢?

Java 还提供了另外一种方式:依赖 Java Agent 技术,修改目标方法的字节码,做到无侵入的埋点。这种利用 Java Agent 的方式的采集器,也叫做探针。在应用程序启动时使用 -javaagent 参数 ,或者运行时使用 attach(pid) 方式,就可以将探针包注入目标应用程序,完成埋点的植入。对业务代码无侵入的方式,可以做到无感的热升级。用户不需要理解深层的原理,就可以使用完整的监控服务

关于字节码的基础知识可以参考美团的这篇文章:

Java Agent 简介

Java Agent 是 Java 1.5 版本之后引⼊的特性,其主要作⽤是在 class 被加载之前对其拦截,已插⼊我们的监听字节码。使用 Java 的Instrumentation 接口(java.lang.instrument)来编写 Agent。

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

这种机制可以认为是虚拟机级别的 AOP,无需对原有应用做任何修改,就可以实现类的动态修改和增强。

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

注意:

无论是通过 Native 的方式还是通过 Java Instrumentation 接口的方式来编写 Agent,它们的工作都是借助 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,transformer 是 ClassFileTransformer 接口的实例,这个接口就只有一个 transform 方法,调用 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):

在这里插入图片描述

Class 装载时序图:

在这里插入图片描述

Java Agent 所使用的 Instrumentation 依赖 JVMTI 实现,当然也可以绕过 Instrumentation 直接使用 JVMTI 实现 Agent。JVMTI 与 JDI 组成了 Java 平台调试体系(JPDA)的主要能力。

Java Agent 使⽤

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

demo 预演

1、创建 POM 项目 Java Agent,项目结构如下:
在这里插入图片描述

2、修改 pom 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.zuozewei</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>javaagent01</artifactId>

    <build>
        <finalName>agent</finalName>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                    </archive>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <outputDirectory>${basedir}</outputDirectory>
                    <archive>
                        <index>true</index>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>com.zuozewei.javaagent01.Agent</Premain-Class>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3、创建 AgentMain 类,实现控制台打印,addTransformer 给 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 <bytes.length
                    && (numRead = is.read(bytes, offset, bytes.length - 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、实现需要修改的类的。

main:

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());
    }

}

Dog:

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 探针库,作为监控服务的提供者,进一步降低了开发成本,不过开发门槛比较高,对测试人员来说有很大的一部分的学习成本。

源码地址:

目录
相关文章
|
24天前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
47 11
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
65 7
|
2月前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
146 6
|
15天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
1天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
Arthas 监控 Java
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
本文介绍了阿里云 Java Agent 4.x 版本在基于 OTel Java Agent 二次开发过程中的实践与思考,并重点从功能、性能、稳定性、兼容性四个方面介绍了所做的工作。同时也介绍了阿里云可观测团队积极参与开源建设取得的丰厚成果。
252 8
拥抱 OpenTelemetry:阿里云 Java Agent 演进实践
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
机器学习/深度学习 传感器 人工智能
2024.11|全球具身智能的端到端AI和具身Agent技术发展到哪里了
2024年,具身智能领域取得显著进展,特别是在端到端AI控制系统和多模态感知技术方面。这些技术不仅推动了学术研究的深入,也为科技公司在实际应用中带来了突破。文章详细介绍了端到端AI的演化、自监督学习的应用、多模态感知技术的突破、基于强化学习的策略优化、模拟环境与现实环境的迁移学习、长程任务规划与任务分解、人机协作与社会交互能力,以及伦理与安全问题。未来几年,具身智能将在多模态感知、自监督学习、任务规划和人机协作等方面继续取得重要突破。
84 2
|
2月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
2月前
|
人工智能 自然语言处理 算法
企业内训|AI/大模型/智能体的测评/评估技术-某电信运营商互联网研发中心
本课程是TsingtaoAI专为某电信运营商的互联网研发中心的AI算法工程师设计,已于近日在广州对客户团队完成交付。课程聚焦AI算法工程师在AI、大模型和智能体的测评/评估技术中的关键能力建设,深入探讨如何基于当前先进的AI、大模型与智能体技术,构建符合实际场景需求的科学测评体系。课程内容涵盖大模型及智能体的基础理论、测评集构建、评分标准、自动化与人工测评方法,以及特定垂直场景下的测评实战等方面。
138 4
下一篇
开通oss服务