一、背景
我们的项目同时使用了 JVM-Sandbox-Repeater(用于流量录制回放)和自定义 JVM-Sandbox 模块(用于特定监控场景)。
由于 Repeater 和 Sandbox Module 遵循沙箱的模块隔离原则,它们运行在独立的类加载器中,无法直接共享数据。这导致了一些关键信息(如traceId)无法在两个组件间传递,限制了更复杂的监控场景实现。
本文将探讨如何突破这一限制,实现 JVM-Sandbox-Repeater 与 Sandbox Module 间的数据共享。
二、理解JVM-Sandbox Module
2.1 模块隔离的特性与挑战
JVM-Sandbox Module 是 JVM-Sandbox(沙箱)容器中实现具体功能逻辑的插件化单元。它利用 JVM-Sandbox 提供的动态字节码增强能力,让你能够以无侵入的方式,在目标 JVM 应用的运行期间,对其特定类的特定方法进行 AOP 拦截和增强。
关键特性:
- 无侵入性:无需修改源码或重启服务
- 动态插拔:模块可实时加载、卸载
- 类隔离:模块间、模块与应用间类加载器隔离
隔离带来的挑战:
- 模块A无法直接访问模块B的类
- 模块无法直接访问业务应用中的自定义类
- 默认情况下,模块间无法共享数据
2.2 模块应用场景
可以把 JVM-Sandbox Module 想象成一个功能强大的"监听器"或"拦截器"。它通过 JVM-Sandbox 容器"附着"到目标 JVM 进程上,监听你感兴趣的方法调用事件(例如方法执行前、返回后、抛出异常时),并在这些事件点执行你自定义的逻辑,比如记录日志、修改参数、模拟异常、统计耗时等。
- 线上故障定位与诊断:类似 Arthas 的功能,例如动态跟踪方法调用参数、返回值、异常信息,排查性能瓶颈等。
- 流量录制与回放(Repeater):录制线上用户的真实请求(入参和出参),用于线下回放测试、故障复现和诊断。阿里开源的 JVM-sandbox-repeater 正是基于此实现的。
- 故障模拟与演练:模拟各种异常场景,如方法超时、抛出特定异常、返回错误结果等,以检验系统的容错性和稳定性。ChaosBlade 的 Java 场景实现就基于 JVM-Sandbox。官方自带的无敌破坏王插件:DebugRalphModule
三、数据共享方案
3.1 难点
- 每个 Module 使用独立的
ModuleJarClassLoader
→ 类彼此隔离(即模块 A 无法直接访问模块 B 的类)。 - 模块默认无法直接访问目标应用中的业务自定义类(如:com.xx.UserService),只能通过沙箱 API 提供的 Advice 对象,利用反射机制获取这些类的信息(如:类名、方法名、参数等)。
3.2 核心思路:全局共享
本次实现的目标是在 Repeater回放过程中,将 traceId 共享给 Sandbox Module 使用。
解决方案的核心是:通过 Bootstrap ClassLoader 加载共享类,创建全局数据,双方通过反射机制访问该类。

3.3 具体步骤
1、定义共享类
通过 mvn package 打包生成 JAR 文件,并将其放置于 JVM-sandbox 目录下
typescript
public class ShareData {
public static BlockingQueue<String> shareQueue = new LinkedBlockingQueue<>();
public static void put(String data) {
shareQueue.add(data);
}
public static void remove(String data) {
shareQueue.remove(data);
}
public static void removeAll() {
shareQueue.remove();
}
public static String get() throws InterruptedException {
return shareQueue.take();
}
}
2、全局加载共享类
修改 AgentLauncher 启动逻辑,确保共享类被 BootClassLoader 加载器加载,使其在全局范围内可访问
java
com.alibaba.jvm.sandbox.agent.AgentLauncher@install
// 将Jar包注入到BootstrapClassLoader
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
"/opt/jvm-sandbox/sandbox/lib/ShareData.jar"
)));
3、Repeater 端写入数据
在回放过程中,通过反射将 traceId 写入共享区
typescript
cn.zcygov.jvm.sandbox.repeater.plugin.core.impl.AbstractRepeater@repeat
static volatile Method putTraceMethod;
//
public String putTrace(String traceId) {
if (putTraceMethod == null) {
synchronized (LOCK) {
if (putTraceMethod == null) {
try {
// 反射调用
putTraceMethod = Class.forName("zcy.ShareData", false, null).getDeclaredMethod("put", String.class);
putTraceMethod.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize traceMethod", e);
}
}
}
}
try {
// 存储
return (String) putTraceMethod.invoke(null,traceId);
} catch (Exception e) {
throw new RuntimeException("Trace invocation failed", e);
}
}
4、Sandbox Module 端读取数据
在自定义模块中,通过反射从共享区读取数据
java
com.alibaba.jvm.sandbox.module.debug.TestModule
static volatile Method getTraceMethod;
public String getTrace() {
if (getTraceMethod == null) {
synchronized (LOCK) {
if (getTraceMethod == null) {
try {
getTraceMethod = Class.forName("zcy.ShareData", false, null).getDeclaredMethod("get");
getTraceMethod.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize traceMethod", e);
}
}
}
}
try {
return (String) getTraceMethod.invoke(null);
} catch (Exception e) {
throw new RuntimeException("Trace invocation failed", e);
}
}
四、尾声
本文介绍了 基于共享类 + BootClassLoader 注入的数据共享机制,解决了 JVM-Sandbox 与 Repeater 因类加载器隔离导致的数据共享问题。 未来可在此机制上扩展更多业务场景,如定制化质量工具、统一调用链追踪等。