前言

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  edHashSet<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. 删除removefor (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 injectResource IfNecessary方法注入@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  edHashSet<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   sun_misc_URLClassPath = unCaughtGetClassDeclaredJavaFieldValue(URLClassLoader.class, "ucp", this);                final   java_util_Collection = unCaughtGetClassDeclaredJavaFieldValue(sun_misc_URLClassPath.getClass(), "loaders", sun_misc_URLClassPath);                for (  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);            }
收藏 打印