我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励欢迎关注微信公众号「架构染色」交流和学习
一、alibaba/jvm-sandbox 概述
alibaba/jvm-sandbox 是 JVM 沙箱容器,一种 JVM 的非侵入式运行期 AOP 解决方案。沙箱容器提供
- 动态增强类你所指定的类,获取你想要的参数和行信息甚至改变方法执行
- 动态可插拔容器框架
在其能力至上构建的上层应用有:
-
- 其chaosblade-io/chaosblade-exec-jvm 是一个基于 jvm-sandbox 的 chaosblade 执行器,用于通过增强类在 Java 应用程序上进行混沌实验。
-
- 基于 JVM-Sandbox 的录制/回放通用解决方案
在《【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 中也有一个开关可以查看增强后的代码,不妨通过调试的方式找一下
四、最后说一句
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。