【alibaba/jvm-sandbox#03】JavaAgent 修改字节码的机制

简介: 开发者一般采用建立一个 Agent 的方式来使用 JVMTI,使用 JVMTI 一个基本的方式就是设置回调函数,在回调函数体内,可以 获取各种各样的VM级信息,甚至控制VM行为,如类加载时修改类
我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励

欢迎关注微信公众号「架构染色」交流和学习

一、alibaba/jvm-sandbox 概述

alibaba/jvm-sandbox 是 JVM 沙箱容器,一种 JVM 的非侵入式运行期 AOP 解决方案。沙箱容器提供

  1. 动态增强类你所指定的类,获取你想要的参数和行信息甚至改变方法执行
  2. 动态可插拔容器框架

在其能力至上构建的上层应用有:

《【alibaba/jvm-sandbox#02】通过无侵入 AOP 实现行为注入和流控》 介绍了 JVM-SANDBOX 属于基于 Instrumentation 的动态编织类的 AOP 框架,通过精心构造了字节码增强逻辑,使得沙箱的模块能在不违反 JDK 约束情况下实现对目标应用方法的无侵入运行时 AOP 拦截。实现行为注入和流控。

刚好有个朋友探讨运行期的 class 信息如何获取。这个问题跟本篇的主题基本一致。

image.png

二、JavaAgent 的执行原理

JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,可以使开发者直接与 C/C++ 以及 JNI 打交道。它是实现 Java 调试器,以及其它 Java 运行态测试与分析工具的基础。

开发者一般采用建立一个 Agent 的方式来使用 JVMTI,使用 JVMTI 一个基本的方式就是设置回调函数,在某些事件发生的时候触发并作出相应的动作。在回调函数体内,可以 获取各种各样的 VM 级信息,注册感兴趣的 VM 事件,甚至控制 VM 行为,如 虚拟机初始化、开始运行、结束,类的加载,方法出入,线程始末等等

其中通过这个 Instrumentation 修改方法字节码 实现收集数据的核心流程如下:
image.png

2.1 两种方式挂载 JavaAgent

1) 启动挂载

所有的类在加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。

2)动态挂载

  • 动态挂载之后,类加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。
  • 动态挂载前 已经加载的类,都未经历增强的处理。
    可通过调用retransformClasses方法,让已加载的类重新加载,
    重新加载时也会执行transform,我们在此方法中增强目标类。

这两个关键方法的完整信息如下:

java.lang.instrument.ClassFileTransformer#transform
java.lang.instrument.Instrumentation#retransformClasses

三、通过 Instrumentation 查看增强后的类

基于事件的行为注入和流控原理如下图,通过以上知识的梳理,我们也可以通过 Instrumentation 查看增强后的类详情。
image.png

通过【alibaba/jvm-sandbox#01】debug 源码的技巧,运行到类转换的代码后

把转换后的 byte[]数组, 通过 evaluate,写入桌面的 1.class 文件.

File file=new File("/Users/kris/GitProject/jvm-sandbox/clock-tinker/target/1.class");
if(!file.exists()) {
    try {
        file.createNewFile();

        FileOutputStream out=new FileOutputStream(file,true);
        out.write(toByteCodeArray);
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

之后再 idea 中打开 1.class 文件,查看文件信息,内容如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.taobao.demo;

import java.com.alibaba.jvm.sandbox.spy.Spy;
import java.com.alibaba.jvm.sandbox.spy.Spy.Ret;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Clock {
    private final SimpleDateFormat clockDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public Clock() {
    }

    final void checkState() {
        throw new IllegalStateException("STATE ERROR!");
    }

    final Date now() {
        return new Date();
    }

    final String report() {
        boolean var10000 = true;
        Ret var10002 = Spy.spyMethodOnBefore(new Object[0], "default", 1001, 1002, "com.taobao.demo.Clock", "report", "()Ljava/lang/String;", this);
        int var10001 = var10002.state;
        if (var10001 == 1) {
            return (String)var10002.respond;
        } else if (var10001 != 2) {
            boolean var2;
            Ret var3;
            int var4;
            try {
                var10000 = true;
                var2 = true;
                var3 = Spy.spyMethodOnReturn("1", "default", 1001);
                var4 = var3.state;
                if (var4 != 1) {
                    if (var4 != 2) {
                        var2 = true;
                        return "1";
                    } else {
                        throw (Throwable)var3.respond;
                    }
                } else {
                    return (String)var3.respond;
                }
            } catch (Throwable var1) {
                var2 = true;
                var3 = Spy.spyMethodOnThrows(var1, "default", 1001);
                var4 = var3.state;
                if (var4 != 1) {
                    if (var4 != 2) {
                        var2 = true;
                        throw var1;
                    } else {
                        throw (Throwable)var3.respond;
                    }
                } else {
                    return (String)var3.respond;
                }
            }
        } else {
            throw (Throwable)var10002.respond;
        }
    }

    final void loopReport() throws InterruptedException {
        while(true) {
            try {
                System.out.println(this.report());
            } catch (Throwable var2) {
                var2.printStackTrace();
            }

            Thread.sleep(1000L);
        }
    }

    public static void main(String... args) throws InterruptedException {
        (new Clock()).loopReport();
    }
}

当然 jvm-sandbox 中也有一个开关可以查看增强后的代码,不妨通过调试的方式找一下

四、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

欢迎点击链接扫马儿关注、交流。

相关文章
|
4月前
|
安全 前端开发 Java
【JVM的秘密揭秘】深入理解类加载器与双亲委派机制的奥秘!
【8月更文挑战第25天】在Java技术栈中,深入理解JVM类加载机制及其双亲委派模型是至关重要的。JVM类加载器作为运行时系统的关键组件,负责将字节码文件加载至内存并转换为可执行的数据结构。其采用层级结构,包括引导、扩展、应用及用户自定义类加载器,通过双亲委派机制协同工作,确保Java核心库的安全性与稳定性。本文通过解析类加载器的分类、双亲委派机制原理及示例代码,帮助读者全面掌握这一核心概念,为开发更安全高效的Java应用程序奠定基础。
94 0
|
2月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
53 3
|
3月前
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
4月前
|
开发者 C# Windows
WPF布局大揭秘:掌握布局技巧,轻松创建响应式用户界面,让你的应用程序更上一层楼!
【8月更文挑战第31天】在现代软件开发中,响应式用户界面至关重要。WPF(Windows Presentation Foundation)作为.NET框架的一部分,提供了丰富的布局控件和机制,便于创建可自动调整的UI。本文介绍WPF布局的基础概念与实现方法,包括`StackPanel`、`DockPanel`、`Grid`等控件的使用,并通过示例代码展示如何构建响应式布局。了解这些技巧有助于开发者优化用户体验,适应不同设备和屏幕尺寸。
101 0
|
4月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
5月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
6月前
|
监控 算法 Java
深入理解Java虚拟机:垃圾收集机制的奥秘
【6月更文挑战第17天】在Java的世界,垃圾收集(GC)是保持内存健康不可或缺的一环。本文将揭开JVM垃圾收集的神秘面纱,探索其原理、算法及调优策略,帮助开发者更好地理解和掌握这一关键技术,确保Java应用的性能与稳定性。
43 5
|
5月前
|
监控 算法 Java
Java虚拟机垃圾收集机制深度解析
在Java的世界中,垃圾收集是确保内存管理高效运行的关键机制之一。本文将深入探讨Java虚拟机的垃圾收集机制,包括其工作原理、常见的垃圾收集算法以及调优实践。我们将基于最新的研究数据和实验结果,提供对垃圾收集器性能的比较分析,并讨论如何根据不同应用场景进行优化。通过逻辑严密的分析,我们旨在为Java开发者提供实用的指导,以帮助他们更好地理解和掌握这一关键技术。
|
6月前
|
安全 前端开发 Java
《JVM由浅入深学习【一】 》JVM由简入深学习提升(类加载过程+父子类加载过程+类加载器+双亲委派机制)
《JVM由浅入深学习【一】 》JVM由简入深学习提升(类加载过程+父子类加载过程+类加载器+双亲委派机制)
43 0
|
6月前
|
存储 缓存 算法
JVM对象创建与内存分配机制
该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
42 0