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