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