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

简介: Jvm-Sandbox源码分析--启动简析 Jvm-Sandbox源码分析--启动时加载模块 Jvm-Sandbox源码分析--增强目标类 在之前三篇文章中我们对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);
            }
目录
相关文章
|
缓存 Java 开发者
JVM15_类的加载、链接、初始化、卸载、主动使用、被动使用(五)
⑦. 过程四:类的Using(使用) ⑧. 过程五:类的Unloading(卸载)
JVM15_类的加载、链接、初始化、卸载、主动使用、被动使用(五)
JVM15_类的加载、链接、初始化、卸载、主动使用、被动使用(四)
⑤. 主动引用(触发在初始化阶段的Clinit方法) ⑥. 被动使用
|
存储 Java 数据库
JVM15_类的加载、链接、初始化、卸载、主动使用、被动使用(一)
①. 说说类加载分几步? ②. 过程一:类的加载(Loading)
JVM15_类的加载、链接、初始化、卸载、主动使用、被动使用(一)
|
IDE Java 编译器
JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)
JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)
228 0
JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)
|
存储 安全 Java
JVM类加载、验证、准备、解析、初始化、卸载过程详解(中)
JVM类加载、验证、准备、解析、初始化、卸载过程详解(中)
184 0
|
Java
JVM类加载、验证、准备、解析、初始化、卸载过程详解(上)
JVM类加载、验证、准备、解析、初始化、卸载过程详解(上)
134 0
JVM类加载、验证、准备、解析、初始化、卸载过程详解(上)
|
9天前
|
Java
JVM之本地内存以及元空间,直接内存的详细解析
JVM之本地内存以及元空间,直接内存的详细解析
25 0
|
1天前
|
存储 Java
深入理解Java虚拟机:JVM内存模型
【4月更文挑战第30天】本文将详细解析Java虚拟机(JVM)的内存模型,包括堆、栈、方法区等部分,并探讨它们在Java程序运行过程中的作用。通过对JVM内存模型的深入理解,可以帮助我们更好地编写高效的Java代码,避免内存溢出等问题。