源码系列 | 阿里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()方法,该方法内包含了诸多操作,比如:
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()方法。
@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()(
在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引用。

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