ClassLoader 能真正隔离 JAR 包内部变量资源,前提是父加载器设为 null、每个模块用独立实例、JAR 路径严格分离、禁止跨模块传递类实例;因类唯一性由全限定名+加载器实例共同决定,故同名类的静态字段和实例状态在内存中完全隔离。

ClassLoader 能真正隔离 JAR 包内部的变量资源,前提是正确绕过双亲委派、使用独立实例、路径严格分离。变量本身不加载,但所属类被不同 ClassLoader 加载后,其静态字段、实例状态在 JVM 中互不共享——这是内存级物理隔离,不是逻辑约定。
变量隔离的本质是类隔离
Java 中没有“变量类”这个概念,只有“含变量的类”。比如 Config、Counter、UserContext 这些类里定义了静态或实例变量。只要这些类由不同的 ClassLoader 加载,哪怕字节码完全一样、包名类名全同,JVM 也视为两个完全无关的类型:
- 它们的 静态变量各自拥有独立内存地址,修改 A 加载的
Counter.count不会影响 B 加载的同名字段; - 它们的 方法区数据(如常量池、字段表)彼此隔离,不会触发交叉初始化;
- 用
==或equals()比较两个同名类的Class对象,结果恒为 false。
实现变量资源隔离的四个硬性条件
仅 new 一个 URLClassLoader 不足以保证隔离,必须同时满足:
-
父加载器显式设为 null:调用
super(urls, null),避免委托给 AppClassLoader 导致类被统一加载; - 每个模块独占一个 ClassLoader 实例:复用加载器 = 共享类 = 变量混用;
- JAR 路径完全隔离:模块 A 的依赖 JAR 不能出现在模块 B 的 classpath 中,否则 findClass 可能误加载;
-
禁止跨模块传递该类实例:比如不能把模块 A 加载的
UserContext对象传给模块 B 的方法,会直接抛ClassCastException。
典型安全隔离场景
以下模式已在生产环境长期验证,变量资源天然隔离:
-
FatJarDelegate + 子加载器:主代理加载器调度,每个插件 JAR 由专属子加载器加载,其中的
RuleConfig、ValidatorHolder静态计数器互不干扰; -
Spring ServiceClassLoader 分层:主应用走 SpringClassLoader,各业务域用独立 ServiceClassLoader 加载自身上下文类,
@Value注入的配置变量完全独立; -
Groovy 脚本动态加载:每个用户上传的 Groovy 脚本及其依赖 JAR,均由独立
GroovyClassLoader加载,脚本内定义的CacheHolder或GlobalFlag全局变量仅对该脚本生命周期有效。
哪些情况会“穿透”隔离
隔离有边界,以下操作可能绕过 ClassLoader 隔离机制:
- 通过
java.lang.String、java.util.Map等系统类传递数据:这些由 Bootstrap ClassLoader 加载,所有模块共享,安全但不隔离; - 使用反射强行访问其他加载器的私有静态字段(需 SecurityManager 放行):技术上可行,但破坏设计契约,不推荐;
- 模块间共用同一个第三方 SDK 的单例工厂(如某日志门面),且该 SDK 内部未按 ClassLoader 做上下文区分:此时变量可能被意外共享。


















