静态代码块只执行一次,但多类加载器会各自触发独立初始化;可通过日志输出类加载器验证;推荐用带锁的静态标志位+类加载器标识控制全局单次初始化。

静态代码块不会“重复执行”——它在类被加载时只运行一次。所谓“重复执行”,实际是多个类加载器各自加载了同一个类,导致每个加载器都触发了一次独立的静态初始化。
根本原因:类加载器隔离
同一个类名,被不同类加载器(如 Tomcat 的 WebAppClassLoader 和 SharedClassLoader、Spring Boot DevTools 的重启类加载器、OSGi 的 BundleClassLoader)分别加载,JVM 会视为**完全不同的类**。它们拥有各自的 Class 对象、各自的静态变量空间、各自独立执行一次静态块。
- 例如:配置文件读取逻辑写在静态块里,两个加载器各跑一遍,就出现“两次加载配置”“两次注册监听器”等现象
- 常见于热部署、微服务模块隔离、插件化架构、测试中反复 reload 类等场景
验证是否真为多加载器问题
在静态块中加一行日志,输出当前类的类加载器身份:
static {
System.out.println("Static init triggered by: " + MyClass.class.getClassLoader());
// ... your init logic
}
- 如果日志打印出两个不同类加载器(如
WebAppClassLoader@1234和ParallelWebappClassLoader@5678),就确认是加载器隔离所致 - 注意:
SystemClassLoader或AppClassLoader通常只有一份;重复出现的往往是自定义或容器级加载器
可靠解法:用 ClassLoader 做初始化守门人
不依赖“类是否已加载”,而是显式控制“某段逻辑全局只执行一次”。推荐使用带锁的静态标志位 + 类加载器标识:
private static volatile boolean initialized = false;
private static final Object initLock = new Object();
static {
ClassLoader loader = MyClass.class.getClassLoader();
synchronized (initLock) {
if (!initialized) {
doActualInit(); // 真正的初始化逻辑
initialized = true;
System.out.println("Initialized once for classloader: " + loader);
}
}
}
- 该方式不依赖 JVM 类加载机制的“单次性”,而是靠应用层控制
- 若需跨类加载器共享状态(如统一配置缓存),应将初始化逻辑移到共享类加载器(如
SystemClassLoader)加载的工具类中 - 避免用
Class.forName(..., true, loader)在静态块中主动触发其他类加载——易引发循环依赖或死锁
替代方案:延迟初始化 + 显式控制
彻底绕开类加载时机不可控的问题,把初始化推迟到第一次真正需要时,并由调用方明确触发:
- 将静态块逻辑封装进一个
public static void initialize()方法 - 所有使用方(如 Spring
@PostConstruct、Servletinit()、主应用启动流程)统一调用它一次 - 方法内部仍可用双重检查锁确保线程安全,且可捕获异常、重试、记录上下文










