前言
实际上,无论是 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 所示。
图1 sandbox的增强策略
实现细节剖析
在 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<String, String> 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 引用。
====== END ======
至此,本文内容全部结束。如果在阅读过程中有任何疑问,欢迎在评论区留言参与讨论。
推荐文章:
码字不易,欢迎转发
评论 (7 条评论)