Jvm-Sandbox源碼分析--模塊刷新和卸載

前言

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);
            }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章