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

一、alibaba/jvm-sandbox 概述

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

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

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

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

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

二、JavaAgent 的执行原理

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

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

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

2.1 两种方式挂载 JavaAgent

1) 启动挂载

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

2)动态挂载

  • 动态挂载之后,类加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。

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

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

java.lang.instrument.ClassFileTransformer#transform
java.lang.instrument.Instrumentation#retransformClasses
复制代码

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

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

通过【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 中也有一个开关可以查看增强后的代码,不妨通过调试的方式找一下

四、最后说一句

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值