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:
- Generate watchId using a global AtomicInteger
- Add SandboxClassFileTransformer to the corresponding module
- Register the SandboxClassFileTransformer with the CoreModule
- Call the addTransformer() method of Instrumentation, and the subsequent class loading will go through SandClassFileTransformer
- Find the collection of classes to render
- 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
- 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:
- Uninstall all user modules
- 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:
- Find the change file through checksumCRC32
- Uninstall all changed user modules
- 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:
- Attempt to freeze module
- Delete from the module registry and mark it as deleted
- Release all releasable resources
- 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); } }