是的,静态代码块在类加载时只执行一次,即在JVM类初始化阶段自动执行且仅执行一次,由初始化标记保证线程安全与唯一性,但该“一次”是针对每个类加载器实例而言。

static 代码块在类加载时真的只执行一次吗
是的,只要类被 JVM 正式加载并完成初始化(即触发 方法),static 代码块就只会执行一次。但要注意:这个“一次”是**针对每个类加载器实例**而言的。如果同一个类被不同类加载器(如自定义 ClassLoader、Tomcat 的 WebAppClassLoader)分别加载,那每个加载器都会独立执行一遍 static 块——这不是 bug,是 JVM 规范行为。
常见误判场景:在 Spring Boot 中反复重启应用(未清空 classloader)、或热部署时看到配置被重复初始化,往往就是类加载器没被回收导致的。
- 确保初始化逻辑不依赖外部可变状态(比如未加锁的全局
Map) - 避免在
static块中调用可能触发类被动初始化的方法(如访问另一个尚未初始化类的static字段) - 若需调试,可在块内加
System.out.println("Config loaded by " + ClassLoader.getSystemClassLoader());
static 块里能读取 application.properties 吗
不能直接读。因为 static 块执行时 Spring 容器还没启动,@Value、Environment 都不可用。想从配置文件加载值,必须绕过 Spring,用原生 Java I/O 或 Properties 类手动解析。
典型做法是把配置放在 src/main/resources/config.properties,然后在 static 块里用 ClassLoader.getResourceAsStream() 加载:
static {
Properties props = new Properties();
try (InputStream is = MyClass.class.getClassLoader()
.getResourceAsStream("config.properties")) {
if (is != null) {
props.load(is);
API_TIMEOUT = Integer.parseInt(props.getProperty("api.timeout", "5000"));
}
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
- 路径用相对路径,不要以
/开头(否则会从 classpath 根目录外查找) - 务必检查
is != null,资源不存在时getResourceAsStream()返回null,不是异常 - 不要在
static块里做耗时操作(如远程 HTTP 请求),会阻塞整个类初始化,影响所有后续使用该类的线程
和 static 变量初始化顺序冲突怎么办
Java 按源码出现顺序依次执行 static 变量赋值和 static 块,同级之间不能跨行引用未声明的变量。比如下面这段会编译失败:
static {
DEFAULT_PORT = 8080; // ❌ 编译错误:非法前向引用
}
static int DEFAULT_PORT;
正确顺序必须是声明在前,初始化在后:
static int DEFAULT_PORT;
static String ENV;
static {
DEFAULT_PORT = Integer.parseInt(System.getProperty("port", "8080"));
ENV = System.getProperty("env", "dev");
}
- 所有
static成员(字段、块)都属于同一初始化阶段,按文本顺序合并进 - 如果某个
static字段依赖另一个类的static值(如OtherClass.VERSION),要确认OtherClass已被加载,否则会触发其初始化,可能引发死锁或意外行为 - 优先用
static final常量替代运行期计算的static变量,更安全也更易优化
为什么 log.info() 在 static 块里没输出
很可能是日志框架(如 Logback、Log4j2)的初始化晚于你的类加载。JVM 不保证类加载顺序,而日志框架自身也有 static 初始化逻辑。如果你的类比日志类先加载,那么 log.info() 实际调用的是未配置的 NOP logger,什么都不会打出来。
验证方式:换成 System.err.println(),如果它能输出,基本确定是日志框架未就绪。
- 最稳妥的做法是不在
static块里打日志;真需要记录,用System.err或写到临时文件 - 如果必须用 SLF4J,确保日志框架 jar 在 classpath 最前面(Maven 中靠前声明)
- Spring 环境下,改用
@PostConstruct或InitializingBean,它们在容器启动后执行,日志可用且可控
类加载时机不可控,static 块的边界比想象中窄——它只管“类准备好”,不管“环境准备好”。真正关键的配置初始化,往往得往后推一步。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











