Bootstrap

源码系列 | 阿里JVM-Sandbox核心源码剖析

前言

实际上,无论是async-profiler还是Arthas,都遵循了JVMTI规范,使之能够使用Instrumentation来构建一个独立于应用程序的Agent程序,以便监测和协助运行在JVM上的程序,并且微乎其微的性能开销,对目标程序带来的损耗极低。

之所以我会对JVM-Sandbox的核心源码进行剖析,其主要原因是在于JVM-Sandbox的开源社区似乎并不活跃,相关资料极其匮乏,许多对JVM-Sandbox设计原理和实现细节感兴趣的同学只能望而却步(比如:避免类污染、冲突等问题的类隔离策略,以及类隔离后的“通讯”操作等)。因此,相信大家仔细阅读本文后,能够有所裨益。

核心原理

JVM-Sandbox将目标方法分解为BEFORE、RETURN、THROWS等三个环节,由此在对应环节上引申出相应的事件探测和流程控制机制。也就是说,通过将这三个环节的事件进行分离,我们可以完成如下3点AOP操作:

  • 动态感知、改变方法的入参;

  • 动态感知、改变方法的出参和抛出异常;

  • 改变方法的执行流程。

这里对改变方法的执行流程做一个简单的介绍。假设我们对目标方法植入了BEFORE、RETURN、THROWS等三个环节的事件增强,那么当方法逻辑执行之前,首先会触发BEFORE事件,待BEFORE事件结束后,通过返回的状态信息(包括状态码、自定义响应结果)来判断方法的后续走向,如果返回的状态码并不是直接返回自定义结果和抛出异常(以限流为例,如果触发限流阈值则直接返回异常信息),则继续执行方法的正常流程(RETURN和THROWS事件同样也具备和BEFORE一样的流程控制机制)。RETURN环节对应的事件在方法执行结束返回前触发。而THROWS事件在方法执行过程中抛出异常时触发。JVM-Sandbox的增强策略,如图1所示。

实现细节剖析

在sandbox-core包下,CoreLauncher类作为JVM-Sandbox的启动器,由它的attachAgent()方法负责attach到目标进程上,如下所示:

private void attachAgent(final String targetJvmPid,
                         final String agentJarPath,
                         final String cfg) throws Exception {
    VirtualMachine vmObj = null;
    try {
        vmObj = VirtualMachine.attach(targetJvmPid);
        if (vmObj != null) {
            vmObj.loadAgent(agentJarPath, cfg);
        }
    } finally {
        if (null != vmObj) {
            vmObj.detach();
        }
    }
}

当成功attache到目标进程上后,会触发Agent-Class的agentmain()方法,这里的Agent-Class对应的就是sandbox-agent包下的AgentLauncher类。agentmain()方法会调用install()方法,该方法内包含了诸多操作,比如:将sandbox-spy包下的Spy类注册到BootstrapClassLoader中创建SandboxClassLoader加载sandbox-core包下的所有类(包括依赖的sandbox-api等),以及反射调用sandbox-core包下的JettyCoreServer.bind()方法启动HTTP服务,如下所示:

private static synchronized InetSocketAddress install(final Map featureMap, 
                                                      final Instrumentation inst) { 
    final String namespace = getNamespace(featureMap); 
    final String propertiesFilePath = getPropertiesFilePath(featureMap); 
    final String coreFeatureString = toFeatureString(featureMap); 
    try { 
        // 将Spy注入到BootstrapClassLoader 
        inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
                getSandboxSpyJarPath(getSandboxHome(featureMap))
                // SANDBOX_SPY_JAR_PATH
        )));
        // 构造自定义的类加载器,尽量减少Sandbox对现有工程的侵蚀 
        final ClassLoader sandboxClassLoader = loadOrDefineClassLoader(
                namespace,
                getSandboxCoreJarPath(getSandboxHome(featureMap))
                // SANDBOX_CORE_JAR_PATH
        );
        // CoreConfigure类定义 
        final Class classOfConfigure = sandboxClassLoader.loadClass(CLASS_OF_CORE_CONFIGURE); 
        // 反序列化成CoreConfigure类实例 
        final Object objectOfCoreConfigure = classOfConfigure.getMethod("toConfigure", String.class, String.class) 
                .invoke(null, coreFeatureString, propertiesFilePath); 
        // CoreServer类定义 
        final Class classOfProxyServer = sandboxClassLoader.loadClass(CLASS_OF_PROXY_CORE_SERVER); 
        // 获取CoreServer单例 
        final Object objectOfProxyServer = classOfProxyServer 
                .getMethod("getInstance") 
                .invoke(null); 
        // CoreServer.isBind() 
        final boolean isBind = (Boolean) classOfProxyServer.getMethod("isBind").invoke(objectOfProxyServer); 
        // 如果未绑定,则需要绑定一个地址 
        if (!isBind) {
            try {
                classOfProxyServer
                        .getMethod("bind", classOfConfigure, Instrumentation.class)
                        .invoke(objectOfProxyServer, objectOfCoreConfigure, inst);
            } catch (Throwable t) {
                classOfProxyServer.getMethod("destroy").invoke(objectOfProxyServer);
                throw t;
            }
        }
        // 返回服务器绑定的地址 
        return (InetSocketAddress) classOfProxyServer 
                .getMethod("getLocal") 
                .invoke(objectOfProxyServer); 
    } catch (Throwable cause) { 
        throw new RuntimeException("sandbox attach failed.", cause); 
    } 
} 

当调用JettyCoreServer.bind()的方法成功启动HTTP服务后,接下来就会触发一系列的调用,直至调用到sandbox-core包的ModuleJarLoader类,由它触发回调函数onLoad()(其实现为DefaultCoreModuleManager的静态内部类InnerModuleLoadCallback)。在InnerModuleLoadCallback.onLoad()方法内部会调用DefaultCoreModuleManager.load()方法触发对Module的加载和注册,如下所示:

private synchronized void load(final String uniqueId,
                               final Module module,
                               final File moduleJarFile,
                               final ModuleJarClassLoader moduleClassLoader) throws ModuleException {
    if (loadedModuleBOMap.containsKey(uniqueId)) {
        logger.debug("module already loaded. module={};", uniqueId);
        return;
    }
    logger.info("loading module, module={};class={};module-jar={};",
            uniqueId,
            module.getClass().getName(),
            moduleJarFile
    );
    // 初始化模块信息
    final CoreModule coreModule = new CoreModule(uniqueId, moduleJarFile, moduleClassLoader, module);
    // 注入@Resource资源
    injectResourceOnLoadIfNecessary(coreModule);
    callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);
    // 设置为已经加载
    coreModule.markLoaded(true);
    // 如果模块标记了加载时自动激活,则需要在加载完成之后激活模块
    markActiveOnLoadIfNecessary(coreModule);
    // 注册到模块列表中
    loadedModuleBOMap.put(uniqueId, coreModule);
    // 通知生命周期,模块加载完成
    callAndFireModuleLifeCycle(coreModule, MODULE_LOAD_COMPLETED);
}

当成功创建ModuleClassLoader(父类加载器为SandboxClassLoader)并加载好Module相关的类后,会由markActiveOnLoadIfNecessary()方法触发DefaultCoreModuleManager的active()方法。这里非常关键,会调用单例类EventListenerHandlers的active()方法将事件监听器(也就是被ModuleClassLoader加载的用户的Module模块的AdviceListener实现)和监听器ID(每个监听器都有一个唯一的监听器ID)绑定到由SandboxClassLoader加载的EventListenerHandlers类的一个全局Map上(mappingOfEventProcessor)。至此为止,可以理解为SandboxClassLoader到ModuleClassLoader的引用关系就构建好了,如下所示:

@Override
public synchronized void active(final CoreModule coreModule) throws ModuleException {
    // 如果模块已经被激活,则直接幂等返回
    if (coreModule.isActivated()) {
        logger.debug("module already activated. module={};", coreModule.getUniqueId());
        return;
    }
    logger.info("active module, module={};class={};module-jar={};",
            coreModule.getUniqueId(),
            coreModule.getModule().getClass().getName(),
            coreModule.getJarFile()
    );
    // 通知生命周期
    callAndFireModuleLifeCycle(coreModule, MODULE_ACTIVE);
    // 激活所有监听器
    for (final SandboxClassFileTransformer sandboxClassFileTransformer : coreModule.getSandboxClassFileTransformers()) {
        EventListenerHandlers.getSingleton().active(
                sandboxClassFileTransformer.getListenerId(),
                sandboxClassFileTransformer.getEventListener(),
                sandboxClassFileTransformer.getEventTypeArray()
        );
    }
    // 标记模块为:已激活
    coreModule.markActivated(true);
}

当触发模块并且JVM-Sandbox成功对目标类进行增强后,JVM-Sandbox会在目标类的方法中进行插装。以BEFORE事件为例,通过反编译代码,我们可以发现在方法逻辑执行之前会优先触发Spy的静态方法spyMethodOnBefore()的调用,如下所示:

public static Ret spyMethodOnBefore(final Object[] argumentArray,
                                    final String namespace,
                                    final int listenerId,
                                    final int targetClassLoaderObjectID,
                                    final String javaClassName,
                                    final String javaMethodName,
                                    final String javaMethodDesc,
                                    final Object target) throws Throwable {
    final Thread thread = Thread.currentThread();
    if (selfCallBarrier.isEnter(thread)) {
        return Ret.RET_NONE;
    }
    final SelfCallBarrier.Node node = selfCallBarrier.enter(thread);
    try {
        final MethodHook hook = namespaceMethodHookMap.get(namespace);
        if (null == hook) {
            return Ret.RET_NONE;
        }
        return (Ret) hook.ON_BEFORE_METHOD.invoke(null,
                listenerId, targetClassLoaderObjectID, SPY_RET_CLASS, javaClassName, javaMethodName, javaMethodDesc, target, argumentArray);
    } catch (Throwable cause) {
        handleException(cause);
        return Ret.RET_NONE;
    } finally {
        selfCallBarrier.exit(thread, node);
    }
}

Spy.spyMethodOnBefore()方法最终会通过反射调用EventListenerHandlers的静态方法onBefore()(在sandbox-core包被加载后,会触发调用SpyUtils的静态方法init()将EventListenerHandlers类的静态方法onBefore()注册到由BootstrapClassLoader加载的Spy类的静态内部类MethodHook的钩子上。这样一来,等于就通过Spy类打通了AppClassLoader、SandboxClassLoader,以及ModuleClassLoader三者之间的“通讯”),由该方法内部触发对handleOnBefore()方法的调用。

在EventListenerHandlers.handleOnBefore()方法中,会根据目标类至Spy.spyMethodOnBefore()传递过来的事件ID从全局的mappingOfEventProcessor中获取出对应的事件处理器,然后调用handleEvent()方法执行事件,如下所示:

private Spy.Ret handleOnBefore(final int listenerId,
                               final int targetClassLoaderObjectID,
                               final String javaClassName,
                               final String javaMethodName,
                               final String javaMethodDesc,
                               final Object target,
                               final Object[] argumentArray) throws Throwable {
    // 获取事件处理器
    final com.alibaba.jvm.sandbox.core.enhance.weaver.EventProcessor wrap = mappingOfEventProcessor.get(listenerId);
    // 如果尚未注册,则直接返回,不做任何处理
    if (null == wrap) {
        logger.debug("listener={} is not activated, ignore processing before-event.", listenerId);
        return newInstanceForNone();
    }
    // 获取调用跟踪信息
    final com.alibaba.jvm.sandbox.core.enhance.weaver.EventProcessor.Process process = wrap.processRef.get();
    // 调用ID
    final int invokeId = invokeIdSequencer.getAndIncrement();
    process.pushInvokeId(invokeId);
    // 调用过程ID
    final int processId = process.getProcessId();
    // 如果当前处理ID被忽略,则立即返回
    if (process.touchIsIgnoreProcess(processId)) {
        process.popInvokeId();
        return newInstanceForNone();
    }
    final ClassLoader javaClassLoader = ObjectIDs.instance.getObject(targetClassLoaderObjectID);
    final BeforeEvent event = process.getEventFactory().makeBeforeEvent(
            processId,
            invokeId,
            javaClassLoader,
            javaClassName,
            javaMethodName,
            javaMethodDesc,
            target,
            argumentArray
    );
    try {
        return handleEvent(listenerId, processId, invokeId, event, wrap);
    } finally {
        process.getEventFactory().returnEvent(event);
    }
}

在handleEvent()方法内部,最终会调用sandbox-api包下的EventListener类的onEvent()—>switchEvent()方法(这里的实现为AdviceAdapterListener)执行事件调用(也就是调用我们的AdviceListener实现),如下所示:

private void switchEvent(final OpStack opStack,
                         final Event event) throws Throwable {
    switch (event.type) {
        case BEFORE: {
            final BeforeEvent bEvent = (BeforeEvent) event;
            final ClassLoader loader = toClassLoader(bEvent.javaClassLoader);
            final Class targetClass = toClass(loader, bEvent.javaClassName);
            final Advice advice = new Advice(
                    bEvent.processId,
                    bEvent.invokeId,
                    toBehavior(
                            targetClass,
                            bEvent.javaMethodName,
                            bEvent.javaMethodDesc
                    ),
                    bEvent.argumentArray,
                    bEvent.target
            );
            final Advice top;
            final Advice parent;
            // 顶层调用
            if (opStack.isEmpty()) {
                top = parent = advice;
            }
            // 非顶层
            else {
                parent = opStack.peek().advice;
                top = parent.getProcessTop();
            }
            advice.applyBefore(top, parent);
            opStackRef.get().pushForBegin(advice);
            adviceListener.before(advice);
            break;
        }
        //省略部分代码
}

最后再总结一下多个类加载器之间的类调用的过程:

  • 首先在BootstrapClassLoader中注册一个Spy类;

  • 在Spy类中绑定由SandboxClassLoader加载的EventListenerHandlers类的方法引用;

  • 在EventListenerHandlers中绑定由ModuleClassLoader加载的用户Module模块的AdviceListenerImpl引用。

欢迎关注公众号

至此,本文内容全部结束。如果在阅读过程中有任何疑问,欢迎在评论区留言参与讨论。



推荐文章:

码字不易,欢迎转发