Skip to content
dukun edited this page Nov 16, 2017 · 26 revisions

BANNER

JVM沙箱容器,JVM的非侵入式运行期AOP解决方案

一、前言

JVM沙箱是什么?

JVM-SANDBOX由纯Java编码完成,基于JVMTI技术规范,为观察和改变代码运行结果提供了即插即用模块接口的容器

在JVM沙箱(以下简称沙箱)的世界观中,任何一次方法的调用都可以分解为BEFORE(进入)、RETURN(返回)和THROWS(异常)三个阶段,由此在三个阶段上引申出对应环节的事件探测和流程控制机制。

// BEFORE
try {

   /*
    * do something...
    */

    // RETURN
    return;

} catch (Throwable cause) {
    // THROWS
}

JVM沙箱能做什么?

基于BEFORE、RETURN和THROWS三个环节事件,可以完成很多类AOP的操作。

  1. 可以感知和改变方法调用的入参
  2. 可以感知和改变方法调用返回值和抛出的异常
  3. 可以改变方法执行的流程
    • 在方法体执行之前直接返回自定义结果对象,原有方法代码将不会被执行
    • 在方法体返回之前重新构造新的结果对象,甚至可以改变为抛出异常
    • 在方法体抛出异常之后重新抛出新的异常,甚至可以改变为正常返回

JVM沙箱做了什么?

  1. 提供了一套能探测事件和改变方法执行流程的API
  2. 提供了一个即插即用的模块管理容器

JVM沙箱都有哪些可能的应用场景

  • 线上故障定位
  • 线上系统流控
  • 线上故障模拟
  • 方法请求录制和结果回放
  • 动态日志打印
  • 安全信息监测和脱敏

JVM沙箱还能帮助你做很多很多,取决于你的脑洞有多大了

二、容器特点

启动方式

沙箱提供了即时加载ATTACH和启动加载AGENT两种启动方式,两种方式均能正确完成沙箱启动及其模块的加载。

  • ATTACH方式启动
    在不需要重启目标JVM的情况下,无感、动态的完成沙箱容器的启动和方法切面的植入

  • AGENT方式加载
    目标JVM启动时完成沙箱容器的启动,并在整个应用启动过程中完成方法切面的植入

C/S架构

  • 沙箱容器采用传统的C/S架构模式,通讯协议考虑到可能要部署到内网,需要NAT网关转发,所以采用了HTTP协议。

    • HTTP-CLIENT:sandbox.sh
    • HTTP-SERVER:沙箱容器(jetty8实现)
  • 在对每个目标JVM进程启动沙箱容器时,容器会主动向目标JVM植入一个agent,容器加载的多个module共享这个agent。

完善的类隔离策略

沙箱通过自定义的SandboxClassLoader破坏了双亲委派的约定,实现了和观察应用的类隔离。所以不用担心加载沙箱会引起应用的类污染、冲突。

各模块之间类通过ModuleClassLoader实现了各自的独立,达到模块之间、模块和沙箱之间、模块和应用之间互不干扰。

沙箱类隔离策略

隔离和通讯

  • 隔离

    • 沙箱通过自定义的SandboxClassLoader破坏了双亲委派的约定,实现了和观察应用的类隔离。所以不用担心加载沙箱会引起应用的类污染、冲突。

    • 沙箱各模块之间类通过ModuleClassLoader实现了各自的独立,达到模块之间、模块和沙箱之间、模块和应用之间互不干扰。

  • 通讯

    • 通过向Bootstrap ClassLoader中注入Spy类,完成观察应用与JVM-Sandbox的通讯
    • 沙箱容器会将事件分发给各个Module,完成容器与Module之间的通讯

    JVM-SANDBOX-CLASSLOADER

字节码动态编织

  • 动态插拔

  • 字节码编织

    • 插入Spy类到字节码中,Spy方法中反射调用JVM-Sandbox的方法。以BeforeEvent为例,进行代码编织展示。

      public int add(int a, int b) {
        try {
      
            // 这里开始产生BEFORE事件
            Object[] argumentArray = new Object[]{a,b};
        Spy.Ret retOnBefore = Spy.onBefore(10001, "com.taobao.test.MyTest", "add", this, argumentArray);
      
            // 这里完成参数替换
            a = argumentArray[0];
            b = argumentArray[1];
      
            // 这里对BEFORE事件的回馈作出响应
            if( retOnBefore.state == I_RETURN ) {
                return (int)retOnBefore.object;
            } else if( retOnBefore.state == I_THROWS ) {
              throws (Throwable)retOnBefore.object;
            }
            // ------ 到此BEFORE事件产生和处理完成
      
            Object r = sum(a,b);
            ...
      
      }
    • 编织完成后,整个代码执行流程将会变成为

      编织后代码执行流程

JVM沙箱整体架构

沙箱一共由三大核心功能组件构成

  1. 代码编织组件
    负责完成预设代码的重写和生效

  2. 事件处理分发组件
    负责完成事件的分发和方法流控控制的执行

  3. 模块管理组件
    负责控制和管理沙箱的各个模块

沙箱的底层提供了一个HTTP-SERVER(Jetty实现),通过HTTP协议完成sandbox.sh和沙箱的控制交互,同时也给各个模块提供了基于HttpServlet和WebSocket规范的API,各模块可以复用沙箱完成各自模块的控制与交互。

整体架构

三、关联文档

常见问题

四、联系我们

JVM沙箱偏向于底层产品,受众面比较窄,问题反馈沟通很可能会因为各种原因造成不及时。所以我们建立了一个钉钉小群,方便大家在这里进行沟通。

钉钉群名片

LOGO

JVM沙箱偏向于底层产品,受众面比较窄,问题反馈沟通很可能会因为各种原因造成不及时。所以我们建立了一个钉钉小群,方便大家在这里进行沟通。

Clone this wiki locally