JVM SandBox source code analysis: enhance the target class, module refresh and module unloading

JVM SandBox source code analysis (I): initialization at startup, module loading at startup, and Http routing with ModuleHttpServlet

4. Enhanced target class

Custom module example:

@MetaInfServices(Module.class)
@Information(id = "TestControllerInterceptor", isActiveOnLoad = false)
public class TestControllerInterceptor extends ModuleLifecycleAdapter implements Module {

    @Resource
    private ModuleEventWatcher moduleEventWatcher;

    @Override
    public void loadCompleted() {
        new EventWatchBuilder(moduleEventWatcher)
                .onClass("com.ppdai.demo.web.TestController")
                .onBehavior("hello").withEmptyParameterTypes()
                .onWatch(new AdviceListener() {
                    @Override
                    protected void before(Advice advice) throws ProcessControlException {
                        System.out.println("sandbox interceptor success");
                    }
                });
    }
}

1) When is the rewritten loadCompleted() called

During module loading, the DefaultCoreModuleManager's load() method will be called

public class DefaultCoreModuleManager implements CoreModuleManager {

		/**
     * Load and register modules
     * <p>1. If the module already exists, the loaded module will be returned</p>
     * <p>2. If the module does not exist, it will be loaded normally</p>
     * <p>3. If the module initialization fails, an exception is thrown</p>
     *
     * @param uniqueId          Module ID
     * @param module            Module object
     * @param moduleJarFile     JAR file of module
     * @param moduleClassLoader ClassLoader responsible for loading modules
     * @throws ModuleException Failed to 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
        );

        // Instantiation module information
        final CoreModule coreModule = new CoreModule(uniqueId, moduleJarFile, moduleClassLoader, module);

        // Inject @ Resource resources
        injectResourceOnLoadIfNecessary(coreModule);

        callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);

        // Set to loaded
        coreModule.markLoaded(true);

        // If the module is marked to activate automatically when loading, you need to activate the module after loading is completed
        markActiveOnLoadIfNecessary(coreModule);

        // Register in module list
        loadedModuleBOMap.put(uniqueId, coreModule);

        // Notify the lifecycle that the module loading is complete
        callAndFireModuleLifeCycle(coreModule, MODULE_LOAD_COMPLETED);

    }

The load() method will finally call the callandfiremodulelife cycle () method to notify the life cycle. When the module is loaded, the incoming life cycle status is MODULE_LOAD_COMPLETED (module loading completed)

public class DefaultCoreModuleManager implements CoreModuleManager {

    /*
     * Notification module lifecycle
     */
    private void callAndFireModuleLifeCycle(final CoreModule coreModule, final ModuleLifeCycleType type) throws ModuleException {
        if (coreModule.getModule() instanceof ModuleLifecycle) {
            final ModuleLifecycle moduleLifecycle = (ModuleLifecycle) coreModule.getModule();
            final String uniqueId = coreModule.getUniqueId();
            switch (type) {

                case MODULE_LOAD: {
                    try {
                        moduleLifecycle.onLoad();
                    } catch (Throwable throwable) {
                        throw new ModuleException(uniqueId, MODULE_LOAD_ERROR, throwable);
                    }
                    break;
                }

                case MODULE_UNLOAD: {
                    try {
                        moduleLifecycle.onUnload();
                    } catch (Throwable throwable) {
                        throw new ModuleException(coreModule.getUniqueId(), MODULE_UNLOAD_ERROR, throwable);
                    }
                    break;
                }

                case MODULE_ACTIVE: {
                    try {
                        moduleLifecycle.onActive();
                    } catch (Throwable throwable) {
                        throw new ModuleException(coreModule.getUniqueId(), MODULE_ACTIVE_ERROR, throwable);
                    }
                    break;
                }

                case MODULE_FROZEN: {
                    try {
                        moduleLifecycle.onFrozen();
                    } catch (Throwable throwable) {
                        throw new ModuleException(coreModule.getUniqueId(), MODULE_FROZEN_ERROR, throwable);
                    }
                    break;
                }

            }// switch
        }

        // Load should be checked here_ Special handling of completed events
        // Because this event processing failure will not affect the module change behavior, only simple log processing is performed
        if (type == MODULE_LOAD_COMPLETED
                && coreModule.getModule() instanceof LoadCompleted) {
            try {
              	// Call the loadCompleted() method of the module
                ((LoadCompleted) coreModule.getModule()).loadCompleted();
            } catch (Throwable cause) {
                logger.warn("loading module occur error when load-completed. module={};", coreModule.getUniqueId(), cause);
            }
        }

    }

In callandfiremodulelife cycle() method, when the lifecycle state is module_ LOAD_ When completed, the loadCompleted() method of the module will be called, which is actually the loadCompleted() method rewritten in the user-defined module

2) , onWatch() method of IBuildingForBehavior

The onWatch() method is finally invoked in the custom module. The method is implemented as follows:

    private class BuildingForBehavior implements IBuildingForBehavior {

        @Override
        public EventWatcher onWatch(AdviceListener adviceListener) {
            return build(new AdviceAdapterListener(adviceListener), null, BEFORE, RETURN, THROWS, IMMEDIATELY_RETURN, IMMEDIATELY_THROWS);
        }

The build() method is invoked in onWatch().

public class EventWatchBuilder {

    private EventWatcher build(final EventListener listener,
                               final Progress progress,
                               final Event.Type... eventTypes) {
				
      	// Call moduleeventwatcher Watch to modify the bytecode of the target class
        final int watchId = moduleEventWatcher.watch(
          			// Create event observation conditions, filter classes and methods
                toEventWatchCondition(),
                listener,
                progress,
                eventTypes
        );

        return new EventWatcher() {

            final List<Progress> progresses = new ArrayList<Progress>();

            @Override
            public int getWatchId() {
                return watchId;
            }

            @Override
            public IBuildingForUnWatching withProgress(Progress progress) {
                if (null != progress) {
                    progresses.add(progress);
                }
                return this;
            }

            @Override
            public void onUnWatched() {
                moduleEventWatcher.delete(watchId, toProgressGroup(progresses));
            }

        };
    }

The build() method calls the watch() method of ModuleEventWatcher to modify the byte code of the target class.

public class DefaultModuleEventWatcher implements ModuleEventWatcher {

    @Override
    public int watch(final EventWatchCondition condition,
                     final EventListener listener,
                     final Progress progress,
                     final Event.Type... eventType) {
        return watch(toOrGroupMatcher(condition.getOrFilterArray()), listener, progress, eventType);
    }

    // Here is the watch remade by matcher
    private int watch(final Matcher matcher,
                      final EventListener listener,
                      final Progress progress,
                      final Event.Type... eventType) {
      	// Generate watchId using a global AtomicInteger
        final int watchId = watchIdSequencer.next();
        // Add ClassFileTransformer to the corresponding module
        final SandboxClassFileTransformer sandClassFileTransformer = new SandboxClassFileTransformer(
                watchId, coreModule.getUniqueId(), matcher, listener, isEnableUnsafe, eventType, namespace);

        // Register with CoreModule
        coreModule.getSandboxClassFileTransformers().add(sandClassFileTransformer);

        // After the transformer is added here, the next class loading will go through sandClassFileTransformer
        inst.addTransformer(sandClassFileTransformer, true);

        // Find the collection of classes to render
        final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform(matcher);
        logger.info("watch={} in module={} found {} classes for watch(ing).",
                watchId,
                coreModule.getUniqueId(),
                waitingReTransformClasses.size()
        );

        int cCnt = 0, mCnt = 0;

        // Progress notification start
        beginProgress(progress, waitingReTransformClasses.size());
        try {

            // Application JVM
            reTransformClasses(watchId,waitingReTransformClasses, progress);

            // count
            cCnt += sandClassFileTransformer.getAffectStatistic().cCnt();
            mCnt += sandClassFileTransformer.getAffectStatistic().mCnt();


            // Activate enhancement class
            if (coreModule.isActivated()) {
                final int listenerId = sandClassFileTransformer.getListenerId();
                EventListenerHandler.getSingleton()
                        .active(listenerId, listener, eventType);
            }

        } finally {
            finishProgress(progress, cCnt, mCnt);
        }

        return watchId;
    }
  
    /*
     * Class affected by deformation observation
     */
    private void reTransformClasses(
        final int watchId,
        final List<Class<?>> waitingReTransformClasses,
        final Progress progress) {
        // Total deformation required
        final int total = waitingReTransformClasses.size();

        // If the class that needs to be re enhanced cannot be found, it will be returned directly
        if (CollectionUtils.isEmpty(waitingReTransformClasses)) {
            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("reTransformClasses={};module={};watch={};",
                    waitingReTransformClasses, coreModule.getUniqueId(), watchId);
        }

        int index = 0;
        for (final Class<?> waitingReTransformClass : waitingReTransformClasses) {
            index++;
            try {
                if (null != progress) {
                    try {
                        progress.progressOnSuccess(waitingReTransformClass, index);
                    } catch (Throwable cause) {
                        // In the process of progress reporting, exceptions are thrown and ignored directly, because it does not affect the main process of deformation
                        // It's just a reporting function
                        logger.warn("watch={} in module={} on {} report progressOnSuccess occur exception at index={};total={};",
                                watchId, coreModule.getUniqueId(), waitingReTransformClass,
                                index - 1, total,
                                cause
                        );
                    }
                }
                inst.retransformClasses(waitingReTransformClass);
                logger.info("watch={} in module={} single reTransform {} success, at index={};total={};",
                        watchId, coreModule.getUniqueId(), waitingReTransformClass,
                        index - 1, total
                );
            } catch (Throwable causeOfReTransform) {
                logger.warn("watch={} in module={} single reTransform {} failed, at index={};total={}. ignore this class.",
                        watchId, coreModule.getUniqueId(), waitingReTransformClass,
                        index - 1, total,
                        causeOfReTransform
                );
                if (null != progress) {
                    try {
                        progress.progressOnFailed(waitingReTransformClass, index, causeOfReTransform);
                    } catch (Throwable cause) {
                        logger.warn("watch={} in module={} on {} report progressOnFailed occur exception, at index={};total={};",
                                watchId, coreModule.getUniqueId(), waitingReTransformClass,
                                index - 1, total,
                                cause
                        );
                    }
                }
            }
        }//for

    }  

The core process of the watch() method of DefaultModuleEventWatcher is as follows:

  1. Generate watchId using a global AtomicInteger
  2. Add SandboxClassFileTransformer to the corresponding module
  3. Register the SandboxClassFileTransformer with the CoreModule
  4. Call the addTransformer() method of Instrumentation, and the subsequent class loading will go through SandClassFileTransformer
  5. Find the collection of classes to render
  6. Call the retransformclasses () method, which actually calls the retransformclasses () method of Instrumentation to trigger class loading again for the classes already loaded by the JVM
  7. Judge whether the default state of the user-defined module is activated. If so, activate the enhanced class

The flow chart of JVM SandBox bytecode enhancement and revocation is as follows:

When bytecode is enhanced, a ClassFileTransformer is registered through the addTransformer(ClassFileTransformer transformer) method of Instrumentation. From then on, class loading is intercepted by ClassFileTransformer, then Instrumentation retransformClasses (Class<. > classes) is invoked to reload class loading for classes loaded by JVM. Class loading will be blocked by classfiletransformer

When the bytecode is enhanced and revoked, the corresponding ClassFileTransformer is removed through the removeTransformer(ClassFileTransformer transformer) method of Instrumentation, and then the Instrumentation retransformClasses (Class< > > classes) is invoked to trigger the class loading again.

5. Module refresh

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

Module refresh is divided into forced refresh and soft refresh

  # -F force flush module
  [[ -n ${OP_MODULE_FORCE_FLUSH} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=true"

  # -f flush module
  [[ -n ${OP_MODULE_FLUSH} ]] &&
    sandbox_curl_with_exit "sandbox-module-mgr/flush" "&force=false"

1) , the flush() method of ModuleMgrModule

@MetaInfServices(Module.class)
@Information(id = "sandbox-module-mgr", author = "luanjia@taobao.com", version = "0.0.2")
public class ModuleMgrModule implements Module {

    @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());
    }

The DefaultCoreModuleManager's flush() method will eventually be called

public class DefaultCoreModuleManager implements CoreModuleManager {

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

Forced refresh is to simply reload all user modules, while soft refresh is to refresh only the changed user modules through file verification

2) . forced refresh

public class DefaultCoreModuleManager implements CoreModuleManager {    

		/**
     * force refresh 
     * Forcibly unload and reload all loaded user modules
     *
     * @throws ModuleException Module operation failed
     */
    private void forceFlush() throws ModuleException {

        logger.info("force-flushing modules:{}", loadedModuleBOMap.keySet());

        // 1. Uninstall the module
        // Collection of modules waiting to be unloaded
        final Collection<CoreModule> waitingUnloadCoreModules = new ArrayList<CoreModule>();

        // Find out all USER modules, so these modules have been uninstalled
        for (final CoreModule coreModule : loadedModuleBOMap.values()) {
            // If it is judged that the module belongs to the USER module directory, it will be added to the module set to be unloaded and unloaded uniformly later
            if (!isSystemModule(coreModule.getJarFile())) {
                waitingUnloadCoreModules.add(coreModule);
            }
        }

        // Record the set of module ID s to be unloaded
        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);
        }

        // Force uninstallation of all modules in the module set waiting to be uninstalled
        for (final CoreModule coreModule : waitingUnloadCoreModules) {
            unload(coreModule, true);
        }

        // 2. Loading module
        // The user module loading directory loads all modules under the user module directory
        // Verify the module access rights
        // User module directory
        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);
            }
        }

    }

The core process of the forceFlush() method of DefaultCoreModuleManager is as follows:

  1. Uninstall all user modules
  2. Load all modules in the user module directory

3) , soft refresh

public class DefaultCoreModuleManager implements CoreModuleManager {    

		/**
     * Soft refresh
     * Find out the changed module files, and only change the modules corresponding to these files
     */
    private void softFlush() {

        logger.info("soft-flushing modules:{}", loadedModuleBOMap.keySet());

        final File systemModuleLibDir = new File(cfg.getSystemModuleLibPath());
        try {
            final ArrayList<File> appendJarFiles = new ArrayList<File>();
            final ArrayList<CoreModule> removeCoreModules = new ArrayList<CoreModule>();
            final ArrayList<Long> checksumCRC32s = new ArrayList<Long>();

            // 1. Find all changed files (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);
                // If CRC32 already exists in the loaded module set, it means that this file has not changed and is ignored
                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);
            }

            // 2. Find out all loaded user modules to be unloaded
            for (final CoreModule coreModule : loadedModuleBOMap.values()) {
                final ModuleJarClassLoader moduleJarClassLoader = coreModule.getLoader();

                // Skip if system module directory
                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;
                }

                // If CRC32 is already in the set to be loaded this time, it means that the file has not changed and is ignored
                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. Delete remove
            for (final CoreModule coreModule : removeCoreModules) {
                unload(coreModule, true);
            }

            // 4. Load add
            for (final File jarFile : appendJarFiles) {
                new ModuleLibLoader(jarFile, cfg.getLaunchMode())
                        .load(new InnerModuleJarLoadCallback(), new InnerModuleLoadCallback());
            }
        } catch (Throwable cause) {
            logger.warn("soft-flushing modules: occur error.", cause);
        }

    }

The core process of DefaultCoreModuleManager's softFlush() method is as follows:

  1. Find the change file through checksumCRC32
  2. Uninstall all changed user modules
  3. Load all changed user modules in the user module directory

7. Module unloading

Module unloading is implemented by the unload() method of DefaultCoreModuleManager:

public class DefaultCoreModuleManager implements CoreModuleManager {

		/**
     * Uninstall and delete the registration module
     * <p>1. If the module does not exist originally, idempotent operation is performed</p>
     * <p>2. If the module exists, try uninstalling</p>
     * <p>3. An attempt is made to freeze the module before uninstalling it</p>
     *
     * @param coreModule              Modules waiting to be unloaded
     * @param isIgnoreModuleException Ignore module exceptions
     * @throws ModuleException Failed to uninstall module
     */
    @Override
    public synchronized CoreModule unload(final CoreModule coreModule,
                                          final boolean isIgnoreModuleException) throws ModuleException {

        if (!coreModule.isLoaded()) {
            logger.debug("module already unLoaded. module={};", coreModule.getUniqueId());
            return coreModule;
        }

        logger.info("unloading module, module={};class={};",
                coreModule.getUniqueId(),
                coreModule.getModule().getClass().getName()
        );

        // Attempt to freeze module
        frozen(coreModule, isIgnoreModuleException);

        // Notification lifecycle
        try {
            callAndFireModuleLifeCycle(coreModule, MODULE_UNLOAD);
        } catch (ModuleException meCause) {
            if (isIgnoreModuleException) {
                logger.warn("unload module occur error, ignored. module={};class={};code={};",
                        meCause.getUniqueId(),
                        coreModule.getModule().getClass().getName(),
                        meCause.getErrorCode(),
                        meCause
                );
            } else {
                throw meCause;
            }
        }

        // Remove from module registry
        loadedModuleBOMap.remove(coreModule.getUniqueId());

        // Mark module as: uninstalled
        coreModule.markLoaded(false);

        // Release all releasable resources
        coreModule.releaseAll();

        // Try closing ClassLoader
        closeModuleJarClassLoaderIfNecessary(coreModule.getLoader());

        return coreModule;
    }

The core process of the unload() method of DefaultCoreModuleManager is as follows:

  1. Attempt to freeze module
  2. Delete from the module registry and mark it as deleted
  3. Release all releasable resources
  4. Attempt to close ModuleJarClassLoader

1) Try to freeze the module

Call the frozen() method to try to freeze the module:

public class DefaultCoreModuleManager implements CoreModuleManager {

		@Override
    public synchronized void frozen(final CoreModule coreModule,
                                    final boolean isIgnoreModuleException) throws ModuleException {

        // If the module has been frozen (not yet activated), it returns directly idempotent
        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()
        );

        // Notification lifecycle
        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;
            }
        }

        // Freeze all listeners
        for (final SandboxClassFileTransformer sandboxClassFileTransformer : coreModule.getSandboxClassFileTransformers()) {
            EventListenerHandler.getSingleton()
                    .frozen(sandboxClassFileTransformer.getListenerId());
        }

        // Mark module as frozen
        coreModule.markActivated(false);
    }

2) Release all releasable resources

Call releaseAll() to release all releasable resources:

public class CoreModule {    

		/**
     * Remove all releasable resources under the current module
     */
    public void releaseAll() {
        synchronized (releaseResources) {
            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. is called in releaseAll(). release() method, which implements the abstract release() method of the inner abstract class ReleaseResource of the sandbox module kernel encapsulation object CoreModule when loading the module and injecting @ Resource resources through the injectResourceOnLoadIfNecessary() method of DefaultCoreModuleManager

                // ModuleEventWatcher object injection
                else if (ModuleEventWatcher.class.isAssignableFrom(fieldType)) {
                    final ModuleEventWatcher moduleEventWatcher = coreModule.append(
                            new ReleaseResource<ModuleEventWatcher>(
                                    SandboxProtector.instance.protectProxy(
                                            ModuleEventWatcher.class,
                                            new DefaultModuleEventWatcher(inst, classDataSource, coreModule, cfg.isEnableUnsafe(), cfg.getNamespace())
                                    )
                            ) {
                                @Override
                                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());
                                        }
                                    }
                                }
                            });

                    writeField(
                            resourceField,
                            module,
                            moduleEventWatcher,
                            true
                    );
                }

After the target module has generated weaving behavior, the rewritten release() method needs to remove the bytecode with enhanced code logic from the Jvm, and then re render the bytecode of the original class. Finally, it calls the delete() method of DefaultModuleEventWatcher:

public class DefaultModuleEventWatcher implements ModuleEventWatcher {    

		@Override
    public void delete(final int watcherId,
                       final Progress progress) {

        final Set<Matcher> waitingRemoveMatcherSet = new LinkedHashSet<Matcher>();

        // Find the filesandboxer to be deleted
        final Iterator<SandboxClassFileTransformer> cftIt = coreModule.getSandboxClassFileTransformers().iterator();
        int cCnt = 0, mCnt = 0;
        while (cftIt.hasNext()) {
            final SandboxClassFileTransformer sandboxClassFileTransformer = cftIt.next();
            if (watcherId == sandboxClassFileTransformer.getWatchId()) {

                // Freeze all associated code enhancements
                EventListenerHandler.getSingleton()
                        .frozen(sandboxClassFileTransformer.getListenerId());

                // Remove the hit ClassFileTransformer from the JVM
                inst.removeTransformer(sandboxClassFileTransformer);

                // count
                cCnt += sandboxClassFileTransformer.getAffectStatistic().cCnt();
                mCnt += sandboxClassFileTransformer.getAffectStatistic().mCnt();

                // Append to filter set to be deleted
                waitingRemoveMatcherSet.add(sandboxClassFileTransformer.getMatcher());

                // Clear the SandboxClassFileTransformer
                cftIt.remove();

            }
        }

        // Find a collection of classes that need to be deleted and re rendered
        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 {
            // Application JVM
            reTransformClasses(watcherId, waitingReTransformClasses, progress);
        } finally {
            finishProgress(progress, cCnt, mCnt);
        }
    }

3) . try to close ModuleJarClassLoader

If all modules loaded by the ModuleJarClassLoader have been unloaded, the ClassLoader needs to shut down actively

public class DefaultCoreModuleManager implements CoreModuleManager {

		/**
     * Close ModuleJarClassLoader
     * If all modules loaded by ModuleJarClassLoader have been unloaded, the ClassLoader needs to be closed actively
     *
     * @param loader ClassLoader that needs to be closed
     */
    private void closeModuleJarClassLoaderIfNecessary(final ClassLoader loader) {

        if (!(loader instanceof ModuleJarClassLoader)) {
            return;
        }

        // Find out whether the registered module still contains the reference of 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();
        }

    }

If a module implements the onJarUnLoadCompleted() method of the module file unloading interface ModuleJarUnLoadSpi, it will receive a message notification at this time to facilitate the module to continue to clean up other resources, such as logback, so as to avoid the failure to close the ClassLoader because the resources are not released

@Stealth
public class ModuleJarClassLoader extends RoutingURLClassLoader {

    public void closeIfPossible() {
        onJarUnLoadCompleted();
        try {

            // If it is the JDK7 + version, URLClassLoader implements the Closeable interface and can be called directly
            if (this instanceof Closeable) {
                logger.debug("JDK is 1.7+, use URLClassLoader[file={}].close()", moduleJarFile);
                try {
                    ((Closeable)this).close();
                } catch (Throwable cause) {
                    logger.warn("close ModuleJarClassLoader[file={}] failed. JDK7+", moduleJarFile, cause);
                }
                return;
            }


            // For the JDK6 version, URLClassLoader is a little troublesome to close. Here we have a lot of code to deal with it
            // Moreover, there is no guarantee that the JAR file handle will be released. At least there is no problem in releasing the JAR file handle
            try {
                logger.debug("JDK is less then 1.7+, use File.release()");
                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);
            }

        } finally {

            // Delete temporary files
            FileUtils.deleteQuietly(tempModuleJarFile);

        }

    }

    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);
        }
    }

Added by greeneel on Fri, 18 Feb 2022 21:01:25 +0200