JVM SandBox实现原理详解

1、什么是JVM SandBox

JVM SandBox(沙箱)实现了一种非侵入式运行期的AOP解决方案。JVM SandBox属于基于Instrumentation的动态编织类的AOP框架,可以在不重启应用的情况下,在运行时完成目标方法的增强和替换,同时沙箱以及沙箱的模块可以随时加载和卸载

主要特性如下

  • 无侵入:目标应用无需重启也无需感知沙箱的存在
  • 类隔离:沙箱以及沙箱的模块不会和目标应用的类相互干扰
  • 可插拔:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹
  • 多租户:目标应用可以同时挂载不同租户下的沙箱并独立控制
  • 高兼容:支持JDK[6,11]

常见应用场景如下

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

2、JVM SandBox实现原理

1)、挂载

JVM SandBox支持通过premain()方法在JVM启动的时候加载;也支持agentmain()方法通过Attach API的方式在JVM启动之后被加载

sandbox-agent模块

public class AgentLauncher {
  
    /**
     * 启动加载
     *
     * @param featureString 启动参数
     *                      [namespace,prop]
     * @param inst          inst
     */
    public static void premain(String featureString, Instrumentation inst) {
        LAUNCH_MODE = LAUNCH_MODE_AGENT;
        install(toFeatureMap(featureString), inst);
    }

    /**
     * 动态加载
     *
     * @param featureString 启动参数
     *                      [namespace,token,ip,port,prop]
     * @param inst          inst
     */
    public static void agentmain(String featureString, Instrumentation inst) {
        LAUNCH_MODE = LAUNCH_MODE_ATTACH;
        final Map<String, String> featureMap = toFeatureMap(featureString);
        writeAttachResult(
                getNamespace(featureMap),
                getToken(featureMap),
                install(featureMap, inst)
        );
    }

JVM Sandbox主要包含SandBox Core、Jetty Server和自定义处理模块三部分

在这里插入图片描述

客户端通过Attach API将沙箱挂载到目标JVM进程上,启动之后沙箱会一直维护着Instrumentation对象引用,通过Instrumentation来修改字节码和重定义类。另外,SandBox启动之后同时会启动一个内部的Jetty服务器,这个服务器用于外部和SandBox进行通信,对模块的加载、卸载、激活、冻结等命令等命令操作都会通过Http请求的方式进行

JVM SandBox包括如下模块

  • sandbox-info:沙箱信息模块,查看当前Sandbox的版本等信息
  • sandbox-module-mgr:沙箱模块管理模块,负责管理管理模块的生命周期(加载、冻结、激活等)
  • sandbox-control:负责卸载sandbox
  • 自定义处理模块扩展

JVM SandBox模块的生命周期

在这里插入图片描述

只有当模块处于激活状态,才会真正调用用户的AOP增强逻辑

2)、类隔离机制

在这里插入图片描述

BootstrapClassLoader加载Spy类(真正织入代码的类)

JVM Sandbox中有两个自定义的ClassLoader:SandBoxClassLoader加载沙箱模块功能,ModuleJarClassLoader加载用户定义模块功能

它们通过重写java.lang.ClassLoaderloadClass(String name, boolean resolve)方法,打破了双亲委派约定,达到与目标类隔离的目的,不会引起应用的类污染、冲突

3)、ClassLoader源码解析

SandBoxClassLoader源码如下:

class SandboxClassLoader extends URLClassLoader {
  
    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
      	//先走一次已加载类的缓存,如果没有命中,则继续往下加载
        final Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) {
            return loadedClass;
        }

        try {
          	//调用URLClassLoader的findClass方法,从自定义的路径中寻找类
            Class<?> aClass = findClass(name);
            if (resolve) {
                resolveClass(aClass);
            }
            return aClass;
        } catch (Exception e) {
          	//没有找到类,在委托AppClassLoader去加载
            return super.loadClass(name, resolve);
        }
    }  

ModuleClassLoader继承了RoutingURLClassLoader,RoutingURLClassLoader中有一个静态内部Routing类,这里传进来的classLoader是SandboxClassLoader,意思是这些指定正则的路径由SandboxClassLoader加载

Routing类的作用如下

Sandbox是允许有多个module的jar包的,每个module分别new一个ModuleClassLoader去加载,jar包里面要用到@Resource注解注入model还有Sandbox的核心类,如果@Resource注解被ModuleClassLoader加载,那一个JVM实例中就会有多个Resource实例,Sandbox内部的核心类也一样。因此这些类只能由SandboxClassLoader加载

    /**
     * 类加载路由匹配器
     */
    public static class Routing {

        private final Collection<String/*REGEX*/> regexExpresses = new ArrayList<String>();
        private final ClassLoader classLoader;

        /**
         * 构造类加载路由匹配器
         *
         * @param classLoader       目标ClassLoader
         * @param regexExpressArray 匹配规则表达式数组
         */
        Routing(final ClassLoader classLoader, final String... regexExpressArray) {
            if (ArrayUtils.isNotEmpty(regexExpressArray)) {
                regexExpresses.addAll(Arrays.asList(regexExpressArray));
            }
            this.classLoader = classLoader;
        }

        /**
         * 当前参与匹配的Java类名是否命中路由匹配规则
         * 命中匹配规则的类加载,将会从此ClassLoader中完成对应的加载行为
         *
         * @param javaClassName 参与匹配的Java类名
         * @return true:命中;false:不命中;
         */
        private boolean isHit(final String javaClassName) {
            for (final String regexExpress : regexExpresses) {
                try {
                    if (javaClassName.matches(regexExpress)) {
                        return true;
                    }
                } catch (Throwable cause) {
                    logger.warn("routing {} failed, regex-express={}.", javaClassName, regexExpress, cause);
                }
            }
            return false;
        }

    }

ModuleClassLoader的父类RoutingURLClassLoader中重写了loadClass(String javaClassName)方法,在module中引用sandbox-core的类由SandboxClassLoader负责加载:

public class RoutingURLClassLoader extends URLClassLoader {
  
    @Override
    protected Class<?> loadClass(final String javaClassName, final boolean resolve) throws ClassNotFoundException {
        return classLoadingLock.loadingInLock(javaClassName, new ClassLoadingLock.ClassLoading() {
            @Override
            public Class<?> loadClass(String javaClassName) throws ClassNotFoundException {
                //优先查询类加载路由表,如果命中路由规则,则优先从路由表中的ClassLoader完成类加载
                if (ArrayUtils.isNotEmpty(routingArray)) {
                    for (final Routing routing : routingArray) {
                        if (!routing.isHit(javaClassName)) {
                            continue;
                        }
                        final ClassLoader routingClassLoader = routing.classLoader;
                        try {
                            return routingClassLoader.loadClass(javaClassName);
                        } catch (Exception cause) {
                            //如果在当前routingClassLoader中找不到应该优先加载的类(应该不可能,但不排除有就是故意命名成同名类)
                            //此时应该忽略异常,继续往下加载
                            //ignore...
                        }
                    }
                }

                //先走一次已加载类的缓存,如果没有命中,则继续往下加载
                final Class<?> loadedClass = findLoadedClass(javaClassName);
                if (loadedClass != null) {
                    return loadedClass;
                }

                try {
                    Class<?> aClass = findClass(javaClassName);
                    if (resolve) {
                        resolveClass(aClass);
                    }
                    return aClass;
                } catch (Exception cause) {
                    DelegateBizClassLoader delegateBizClassLoader = BusinessClassLoaderHolder.getBussinessClassLoader();
                    try {
                        if(null != delegateBizClassLoader){
                            return delegateBizClassLoader.loadClass(javaClassName,resolve);
                        }
                    } catch (Exception e) {
                        //忽略异常,继续往下加载
                    }
                    return RoutingURLClassLoader.super.loadClass(javaClassName, resolve);
                }
            }
        });
    }  

ModuleClassLoader类加载流程如下

在这里插入图片描述

4)、SandBox初始化流程

SandBox初始化流程如下图

在这里插入图片描述

5)、类增强策略

在这里插入图片描述

SandBox通过在BootstrapClassLoader中埋藏的Spy类完成目标类和沙箱内核的通讯,最终执行到用户模块的AOP方法

6)、字节码增强和撤销流程

在这里插入图片描述

字节码增强时,通过Instrumentation的addTransformer(ClassFileTransformer transformer)方法注册一个ClassFileTransformer,从此之后的类加载都会被ClassFileTransformer拦截,然后调用Instrumentation的retransformClasses(Class<?>... classes)对JVM已经加载的类重新触发类加载,类加载时会被ClassFileTransformer拦截

字节码增强撤销时,通过Instrumentation的removeTransformer(ClassFileTransformer transformer)方法移除相应的ClassFileTransformer,然后调用Instrumentation的retransformClasses(Class<?>... classes)重新触发类加载

推荐JVM SandBox文章

JVM源码分析之javaagent原理完全解读

JVM SandBox模块编写example

JVM SandBox之调用方式(命令行和http)

JVM SandBox的技术原理与应用分析

JVM Sandbox源码分析–启动简析

JVM Sandbox源码分析–启动时加载模块

JVM Sandbox源码分析–增强目标类

JVM Sandbox源码分析–模块刷新和卸载

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邋遢的流浪剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值