您现在的位置是:首页 > 文章详情

Jvm-Sandbox源码分析--模块刷新和卸载

日期:2019-09-09点击:636

前言

Jvm-Sandbox源码分析--启动简析
Jvm-Sandbox源码分析--启动时加载模块
Jvm-Sandbox源码分析--增强目标类
在之前三篇文章中我们对jvm-sandbox相关逻辑做了一些分析,本篇文章将要分析一下模块刷新,模块卸载的逻辑。

执行脚本

sh sandbox/bin/sandbox.sh -p pid -f/-F

刷新分为强制刷新和非强制刷新两种类型。

# -F force flush module [[ ! -z ${OP_MODULE_FORCE_FLUSH} ]] \ && sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=true" # -f flush module [[ ! -z ${OP_MODULE_FLUSH} ]] \ && sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=false"

ModuleMgrModule flush方法

http请求都要通过ModuleHttpServlet doMethod方法路由,前面分析过,这里不再赘述,我们直接看目标模块的代码。

@Command("flush") public void flush(final Map<String, String> param, final PrintWriter writer) throws ModuleException { final String isForceString = getParamWithDefault(param, "force", EMPTY); final boolean isForce = BooleanUtils.toBoolean(isForceString); moduleManager.flush(isForce); output(writer, "module flush finished, total=%s;", moduleManager.list().size()); }

DefaultCoreModuleManager flush方法

@Override public synchronized void flush(final boolean isForce) throws ModuleException { if (isForce) { forceFlush(); } else { softFlush(); } }

强制刷新和非强制刷新,逻辑上区别只是强制刷新简单粗暴重新卸载加载所有的用户模块,非强制刷新则是通过文件校验只刷新变化的用户模块。下面我们简单看一下这两种方式的实现。

强制刷新forceFlush

1.卸载所有模块

// 1. 卸载模块 // 等待卸载的模块集合 final Collection<CoreModule> waitingUnloadCoreModules = new ArrayList<CoreModule>(); // 找出所有USER的模块,所以这些模块都卸载了 for (final CoreModule coreModule : loadedModuleBOMap.values()) { // 如果判断是属于USER模块目录下的模块,则加入到待卸载模块集合,稍后统一进行卸载 if (!isSystemModule(coreModule.getJarFile())) { waitingUnloadCoreModules.add(coreModule); } } // 记录下即将被卸载的模块ID集合 if (logger.isInfoEnabled()) { final Set<String> uniqueIds = new LinkedHashSet<String>(); for (final CoreModule coreModule : waitingUnloadCoreModules) { uniqueIds.add(coreModule.getUniqueId()); } logger.info("force-flush modules: will be unloading modules : {}", uniqueIds); } // 强制卸载掉所有等待卸载的模块集合中的模块 for (final CoreModule coreModule : waitingUnloadCoreModules) { unload(coreModule, true); }

2.加载模块

 // 用户模块加载目录,加载用户模块目录下的所有模块 // 对模块访问权限进行校验 // 用户模块目录 final File[] userModuleLibFileArray = cfg.getUserModuleLibFiles(); for (final File userModuleLibDir : userModuleLibFileArray) { if (userModuleLibDir.exists() && userModuleLibDir.canRead()) { logger.info("force-flush modules: module-lib={}", userModuleLibDir); new ModuleLibLoader(userModuleLibDir, cfg.getLaunchMode()) .load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback()); } else { logger.warn("force-flush modules: module-lib can not access, will be ignored. module-lib={}", userModuleLibDir); } } 

非强制刷新softFlush

//待加载文件集合 final ArrayList<File> appendJarFiles = new ArrayList<File>(); //待卸载模块集合 final ArrayList<CoreModule> removeCoreModules = new ArrayList<CoreModule>(); //待检查文件checksumCRC32集合 final ArrayList<Long> checksumCRC32s = new ArrayList<Long>();

1.通过 checksumCRC32 找出变动文件

/** // 1. 找出所有有变动的文件(add/remove) for (final File jarFile : cfg.getUserModuleLibFiles()) { final long checksumCRC32; try { checksumCRC32 = FileUtils.checksumCRC32(jarFile); } catch (IOException cause) { logger.warn("soft-flushing module: compute module-jar CRC32 occur error. module-jar={};", jarFile, cause); continue; } checksumCRC32s.add(checksumCRC32); // 如果CRC32已经在已加载的模块集合中存在,则说明这个文件没有变动,忽略 if (isChecksumCRC32Existed(checksumCRC32)) { logger.info("soft-flushing module: module-jar is not changed, ignored. module-jar={};CRC32={};", jarFile, checksumCRC32); continue; } logger.info("soft-flushing module: module-jar is changed, will be flush. module-jar={};CRC32={};", jarFile, checksumCRC32); appendJarFiles.add(jarFile); } private boolean isChecksumCRC32Existed(long checksumCRC32) { for (final CoreModule coreModule : loadedModuleBOMap.values()) { if (coreModule.getLoader().getChecksumCRC32() == checksumCRC32) { return true; } } return false; }

2.找出所有待卸载的已加载用户模块

 for (final CoreModule coreModule : loadedModuleBOMap.values()) { final ModuleJarClassLoader moduleJarClassLoader = coreModule.getLoader(); // 如果是系统模块目录则跳过 if (isOptimisticDirectoryContainsFile(systemModuleLibDir, coreModule.getJarFile())) { logger.debug("soft-flushing module: module-jar is in system-lib, will be ignored. module-jar={};system-lib={};", coreModule.getJarFile(), systemModuleLibDir ); continue; } // 如果CRC32已经在这次待加载的集合中,则说明这个文件没有变动,忽略 if (checksumCRC32s.contains(moduleJarClassLoader.getChecksumCRC32())) { logger.info("soft-flushing module: module-jar already loaded, ignored. module-jar={};CRC32={};", coreModule.getJarFile(), moduleJarClassLoader.getChecksumCRC32() ); continue; } logger.info("soft-flushing module: module-jar is changed, module will be reload/remove. module={};module-jar={};", coreModule.getUniqueId(), coreModule.getJarFile() ); removeCoreModules.add(coreModule); }

3.卸载模块

// 3. 删除remove for (final CoreModule coreModule : removeCoreModules) { unload(coreModule, true); }

4.加载模块

for (final File jarFile : appendJarFiles) { new ModuleLibLoader(jarFile, cfg.getLaunchMode()) .load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback()); }

卸载模块

这里我们一起分析DefaultCoreModuleManager unload方法中卸载模块流程。
1.尝试冻结模块

 // 尝试冻结模块 frozen(coreModule, isIgnoreModuleException); public synchronized void frozen(final CoreModule coreModule, final boolean isIgnoreModuleException) throws ModuleException { // 如果模块已经被冻结(尚未被激活),则直接幂等返回 if (!coreModule.isActivated()) { logger.debug("module already frozen. module={};", coreModule.getUniqueId()); return; } logger.info("frozen module, module={};class={};module-jar={};", coreModule.getUniqueId(), coreModule.getModule().getClass().getName(), coreModule.getJarFile() ); // 通知生命周期 try { callAndFireModuleLifeCycle(coreModule, MODULE_FROZEN); } catch (ModuleException meCause) { if (isIgnoreModuleException) { logger.warn("frozen module occur error, ignored. module={};class={};code={};", meCause.getUniqueId(), coreModule.getModule().getClass().getName(), meCause.getErrorCode(), meCause ); } else { throw meCause; } } // 冻结所有监听器 for (final SandboxClassFileTransformer sandboxClassFileTransformer : coreModule.getSandboxClassFileTransformers()) { EventListenerHandlers.getSingleton() .frozen(sandboxClassFileTransformer.getListenerId()); } // 标记模块为:已冻结 coreModule.markActivated(false); }

2.从模块注册表中删除,并打上删除标记

// 从模块注册表中删除 loadedModuleBOMap.remove(coreModule.getUniqueId()); // 标记模块为:已卸载 coreModule.markLoaded(false);

3.释放所有可释放资源

 // 释放所有可释放资源 coreModule.releaseAll(); // 模块所持有的可释放资源 private final List<ReleaseResource<?>> releaseResources = new ArrayList<ReleaseResource<?>>(); /** * 在当前模块下移除所有可释放资源 */ public void releaseAll() { final Iterator<ReleaseResource<?>> resourceRefIt = releaseResources.iterator(); while (resourceRefIt.hasNext()) { final ReleaseResource<?> resourceRef = resourceRefIt.next(); resourceRefIt.remove(); if (null != resourceRef) { logger.debug("release resource={} in module={}", resourceRef.get(), uniqueId); try { resourceRef.release(); } catch (Exception cause) { logger.warn("release resource occur error in module={};", uniqueId, cause); } } } }

注意这段代码中有一段调用

resourceRef.release();

这个方法是我们在加载模块,通过DefaultCoreModuleManager injectResourceOnLoadIfNecessary方法注入@Resource资源的时候,实现沙箱模块内核封装对象CoreModule内部抽象类ReleaseResource的抽象release方法。

public void release() { logger.info("release all SandboxClassFileTransformer for module={}", coreModule.getUniqueId()); final ModuleEventWatcher moduleEventWatcher = get(); if (null != moduleEventWatcher) { for (final SandboxClassFileTransformer sandboxClassFileTransformer : new ArrayList<SandboxClassFileTransformer>(coreModule.getSandboxClassFileTransformers())) { moduleEventWatcher.delete(sandboxClassFileTransformer.getWatchId()); } } } 

这段代码是在目标模块已经产生织入行为之后,要从jvm去掉我们之前带有增强的代码逻辑的字节码,然后再重新渲染一次原始类的字节码。

public void delete(final int watcherId, final Progress progress) { final Set<Matcher> waitingRemoveMatcherSet = new LinkedHashSet<Matcher>(); // 找出待删除的SandboxClassFileTransformer final Iterator<SandboxClassFileTransformer> cftIt = coreModule.getSandboxClassFileTransformers().iterator(); int cCnt = 0, mCnt = 0; while (cftIt.hasNext()) { final SandboxClassFileTransformer sandboxClassFileTransformer = cftIt.next(); if (watcherId == sandboxClassFileTransformer.getWatchId()) { // 冻结所有关联代码增强 EventListenerHandlers.getSingleton() .frozen(sandboxClassFileTransformer.getListenerId()); // 在JVM中移除掉命中的ClassFileTransformer inst.removeTransformer(sandboxClassFileTransformer); // 计数 cCnt += sandboxClassFileTransformer.getAffectStatistic().cCnt(); mCnt += sandboxClassFileTransformer.getAffectStatistic().mCnt(); // 追加到待删除过滤器集合 waitingRemoveMatcherSet.add(sandboxClassFileTransformer.getMatcher()); // 清除掉该SandboxClassFileTransformer cftIt.remove(); } } // 查找需要删除后重新渲染的类集合 final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform( new GroupMatcher.Or(waitingRemoveMatcherSet.toArray(new Matcher[0])) ); logger.info("watch={} in module={} found {} classes for delete.", watcherId, coreModule.getUniqueId(), waitingReTransformClasses.size() ); beginProgress(progress, waitingReTransformClasses.size()); try { // 应用JVM reTransformClasses(watcherId, waitingReTransformClasses, progress); } finally { finishProgress(progress, cCnt, mCnt); } }

4.尝试关闭ModuleJarClassLoader
如ModuleJarClassLoader所加载上来的所有模块都已经被卸载,则该ClassLoader需要主动进行关闭

/** * 关闭ModuleJarClassLoader * 如ModuleJarClassLoader所加载上来的所有模块都已经被卸载,则该ClassLoader需要主动进行关闭 * * @param loader 需要被关闭的ClassLoader */ private void closeModuleJarClassLoaderIfNecessary(final ClassLoader loader) { if (!(loader instanceof ModuleJarClassLoader)) { return; } // 查找已经注册的模块中是否仍然还包含有ModuleJarClassLoader的引用 boolean hasRef = false; for (final CoreModule coreModule : loadedModuleBOMap.values()) { if (loader == coreModule.getLoader()) { hasRef = true; break; } } if (!hasRef) { logger.info("ModuleJarClassLoader will be close: all module unloaded.", loader); ((ModuleJarClassLoader) loader).closeIfPossible(); } }

如果有模块实现模块文件卸载接口ModuleJarUnLoadSpi的onJarUnLoadCompleted方法,则会在这时收到消息通知,方便模块继续清理其他资源,如logback,避免因为资源未释放,导致classLoader关闭失败。

private void onJarUnLoadCompleted() { try { final ServiceLoader<ModuleJarUnLoadSpi> moduleJarUnLoadSpiServiceLoader = ServiceLoader.load(ModuleJarUnLoadSpi.class, this); for (final ModuleJarUnLoadSpi moduleJarUnLoadSpi : moduleJarUnLoadSpiServiceLoader) { logger.info("unloading module-jar: onJarUnLoadCompleted() loader={};moduleJarUnLoadSpi={};", this, getJavaClassName(moduleJarUnLoadSpi.getClass()) ); moduleJarUnLoadSpi.onJarUnLoadCompleted(); } } catch (Throwable cause) { logger.warn("unloading module-jar: onJarUnLoadCompleted() occur error! loader={};", this, cause); } }

针对jdk7+版本 URLClassLoader实现了Closeable接口,直接调用即可。

 if (this instanceof Closeable) { logger.debug("JDK is 1.7+, use URLClassLoader[file={}].close()", moduleJarFile); try { final Method closeMethod = unCaughtGetClassDeclaredJavaMethod(URLClassLoader.class, "close"); closeMethod.invoke(this); } catch (Throwable cause) { logger.warn("close ModuleJarClassLoader[file={}] failed. JDK7+", moduleJarFile, cause); } return; }

针对jdk6版本,仅关闭 jar 句柄

// 对于JDK6的版本,URLClassLoader要关闭起来就显得有点麻烦,这里弄了一大段代码来稍微处理下 // 而且还不能保证一定释放干净了,至少释放JAR文件句柄是没有什么问题了 try { logger.debug("JDK is less then 1.7+, use File.release()", moduleJarFile); final Object sun_misc_URLClassPath = unCaughtGetClassDeclaredJavaFieldValue(URLClassLoader.class, "ucp", this); final Object java_util_Collection = unCaughtGetClassDeclaredJavaFieldValue(sun_misc_URLClassPath.getClass(), "loaders", sun_misc_URLClassPath); for (Object sun_misc_URLClassPath_JarLoader : ((Collection) java_util_Collection).toArray()) { try { final JarFile java_util_jar_JarFile = unCaughtGetClassDeclaredJavaFieldValue( sun_misc_URLClassPath_JarLoader.getClass(), "jar", sun_misc_URLClassPath_JarLoader ); java_util_jar_JarFile.close(); } catch (Throwable t) { // if we got this far, this is probably not a JAR loader so skip it } } } catch (Throwable cause) { logger.warn("close ModuleJarClassLoader[file={}] failed. probably not a HOTSPOT VM", moduleJarFile, cause); }
原文链接:https://yq.aliyun.com/articles/717965
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章