Virtual Threads 与 CRaC
概述
Spring Boot 3.2+ 引入了对 Java 21 Virtual Threads(虚拟线程)和 CRaC(Coordinated Restore at Checkpoint)的原生支持。虚拟线程通过 spring.threads.virtual.enabled 快捷开关即可启用,让 Tomcat 等 Web 服务器以轻量级虚拟线程处理请求;CRaC 则允许将运行中的应用状态持久化到磁盘,在后续启动时从检查点快速恢复。
本文将深入拆解 Virtual Threads 与 CRaC 的 8 个关键细节,涵盖自动配置原理、执行器选择、Tomcat 线程模型变更、CRaC 检查点/恢复机制等核心内容。
本文基于 Spring Boot 3.2.5 + Java 21 + CRaC 1.x 源码分析。
1. TomcatProtocolHandlerCustomizer 配置 Virtual Threads
在 Spring Boot 3.2 启用虚拟线程时,通过 TomcatProtocolHandlerCustomizer 将 Tomcat 的 Http11NioProtocol 的默认执行器替换为虚拟线程执行器。
@AutoConfiguration(before = TomcatServletWebServerFactory.class)
@ConditionalOnProperty(prefix = "spring.threads.virtual", name = "enabled", havingValue = "true")
public class VirtualThreadAutoConfiguration {
@Bean
@ConditionalOnClass(ProtocolHandler.class)
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
// 返回一个 Customizer,将 Tomcat 的协议处理器执行器替换为虚拟线程执行器
return (protocolHandler) -> {
// 设置 Tomcat 的请求处理执行器 = 虚拟线程每任务执行器
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}TomcatProtocolHandlerCustomizer 接口:
@FunctionalInterface
public interface TomcatProtocolHandlerCustomizer<T extends ProtocolHandler> {
void customize(T protocolHandler);
}TomcatServletWebServerFactory 中的应用:
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory {
private List<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers;
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
// 应用所有的 ProtocolHandlerCustomizer(包括虚拟线程执行器设置)
ProtocolHandler handler = connector.getProtocolHandler();
for (TomcatProtocolHandlerCustomizer<?> customizer : this.tomcatProtocolHandlerCustomizers) {
((TomcatProtocolHandlerCustomizer<ProtocolHandler>) customizer).customize(handler);
}
// ...
}
}效果:每个 HTTP 请求由虚拟线程处理,而非平台线程池中的线程。
2. spring.threads.virtual.enabled=true 快捷开关
Spring Boot 3.2+ 提供了 spring.threads.virtual.enabled=true 的快捷开关,自动配置虚拟线程支持。
# application.properties
spring.threads.virtual.enabled=true配置背后的自动配置类:
@AutoConfiguration
@ConditionalOnProperty(prefix = "spring.threads.virtual", name = "enabled", havingValue = "true")
public class VirtualThreadAutoConfiguration {
// Tomcat 协议处理器
@Bean
@ConditionalOnClass(ProtocolHandler.class)
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return (handler) -> handler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
// Netty 协议处理器(WebFlux + Reactor Netty)
@Bean
@ConditionalOnClass(ReactorNetty.class)
public ReactorNettyVirtualThreadsCustomizer reactorNettyVirtualThreadsCustomizer() {
return new ReactorNettyVirtualThreadsCustomizer();
}
// AsyncConfigurer 自定义
@Bean
@ConditionalOnMissingBean(AsyncConfigurer.class)
public AsyncConfigurerCustomizer asyncConfigurerCustomizer() {
// 使 @Async 方法也使用虚拟线程
return configurer -> configurer.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
}生效范围:
| 组件 | 虚拟化方式 |
|---|---|
| Tomcat 请求处理线程 | protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor()) |
| WebFlux + Reactor Netty | ReactorNettyVirtualThreadsCustomizer |
@Async 异步方法 | AsyncConfigurerCustomizer 配置虚拟线程执行器 |
| Spring MVC 异步请求 | Callable / WebAsyncTask 也使用虚拟线程 |
与 Java 21 的关系:
Java 21+
↓
虚拟线程(Project Loom)成熟
↓
JEP 444: VirtualThreadPerTaskExecutor
↓
Executors.newVirtualThreadPerTaskExecutor()
↓
Spring Boot 3.2: spring.threads.virtual.enabled=true3. ThreadPerTaskExecutor vs VirtualThreadExecutor
Spring Boot 对不同执行器做了抽象,实现虚拟线程与非虚拟线程的切换。
| 执行器 | 创建方式 | 线程类型 | 使用场景 |
|---|---|---|---|
ThreadPerTaskExecutor | Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory()) | 平台线程 | 未启用虚拟线程时的默认 |
VirtualThreadExecutor | Executors.newVirtualThreadPerTaskExecutor() | 虚拟线程 | 启用虚拟线程后 |
ThreadPerTaskExecutor(非虚拟线程):
// Java 21 新增
public class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory;
public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
@Override
public void execute(Runnable command) {
// 每次 execute() 创建一个新平台线程
threadFactory.newThread(command).start();
}
}Executors.newVirtualThreadPerTaskExecutor():
// java.util.concurrent.Executors
public static ExecutorService newVirtualThreadPerTaskExecutor() {
// 创建一个 ExecutorService,每次 task 创建一个新的虚拟线程
// 使用 Thread.ofVirtual().factory() 创建虚拟线程
return newVirtualThreadPerTaskExecutor(Thread.ofVirtual().factory());
}内部实现对比:
// 平台线程版本(未启用虚拟线程)
// Tomcat 使用 StandardThreadExecutor 线程池
// 核心线程数 = server.tomcat.threads.min-spare(默认 10)
// 最大线程数 = server.tomcat.threads.max(默认 200)
// 虚拟线程版本(启用虚拟线程后)
// Tomcat 使用 Executors.newVirtualThreadPerTaskExecutor()
// 每次请求创建一个新的虚拟线程
// 无上限,JVM 自动管理虚拟线程的挂载/卸载4. Virtual Threads 下 Tomcat 的 maxThreads 参数无效
启用虚拟线程后,Tomcat 的 server.tomcat.threads.max 和 server.tomcat.threads.min-spare 参数被忽略。
// TomcatProtocolHandlerCustomizer 的调用链
// 当 protocolHandler.setExecutor(executor) 被设为虚拟线程执行器后:
public abstract class AbstractProtocol<S> implements ProtocolHandler {
private Executor executor; // 被设置为 VirtualThreadPerTaskExecutor
public void setExecutor(Executor executor) {
this.executor = executor;
}
// Tomcat 在处理请求时从这个 executor 获取线程
// 如果是虚拟线程执行器,则不再使用内部的 StandardThreadExecutor
// 因此 maxThreads / minSpareThreads 等参数不再生效
protected Processor createProcessor() {
// 如果 executor 是虚拟线程执行器,忽略 maxThreads
return new Http11Processor(...);
}
}参数影响:
| 参数 | 平台线程模式 | 虚拟线程模式 |
|---|---|---|
server.tomcat.threads.max | 生效(默认 200) | 忽略 |
server.tomcat.threads.min-spare | 生效(默认 10) | 忽略 |
server.tomcat.max-connections | 生效(默认 8192) | 生效(连接级限制) |
server.tomcat.accept-count | 生效(默认 100) | 生效 |
虚拟线程模式下,每个请求对应一个虚拟线程,虚拟线程数量无上限(依赖 JVM 的 Carrier 线程调度)。但
max-connections和accept-count仍然是系统资源瓶颈。
5. Virtual Threads + @Async 的配合
启用虚拟线程后,@Async 注解的方法也会自动使用虚拟线程执行器。
// AsyncConfigurerCustomizer 自动配置
@Bean
@ConditionalOnMissingBean(AsyncConfigurer.class)
public AsyncConfigurerCustomizer asyncConfigurerCustomizer() {
// 将 AsyncConfigurer 的执行器设为虚拟线程执行器
return configurer -> configurer.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}使用示例:
@Component
public class AsyncService {
@Async // 使用虚拟线程执行
public CompletableFuture<String> doSomething() {
// 当前运行在虚拟线程中
System.out.println(Thread.currentThread().isVirtual()); // true
Thread.sleep(1000); // 虚拟线程自动让出 carrier 线程
return CompletableFuture.completedFuture("done");
}
}自定义 AsyncConfigurer:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// 完全自定义虚拟线程执行器
return Executors.newVirtualThreadPerTaskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步方法执行异常: {}#{}", method.getDeclaringClass().getSimpleName(), method.getName(), ex);
};
}
}AsyncConfigurerCustomizer 源码:
@FunctionalInterface
public interface AsyncConfigurerCustomizer {
void customize(AsyncConfigurer configurer);
// 默认实现
default void setExecutor(Executor executor) {
// 通过反射设置 AsyncConfigurer 的 executor
}
}6. CRaC CheckpointController.checkpoint() 流程
CRaC(Coordinated Restore at Checkpoint)是 OpenJDK 项目,允许 Java 应用在运行中创建检查点,将完整的 JVM 状态(堆、线程、资源)持久化到磁盘,后续从该检查点快速恢复。
Spring Boot 通过 spring-boot-starter-crac 提供 CRaC 支持。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-crac</artifactId>
</dependency>CheckpointController 核心实现:
public class CheckpointController {
// 触发检查点
public void checkpoint() {
// 1. 通知所有 @Checkpoint 回调
beforeCheckpoint();
// 2. 执行 CRaC 底层 checkpoint
try {
// 调用 JVM 的 CRaC API 创建检查点
// 这会将完整的 JVM 堆和线程状态写入磁盘
org.crac.Core.checkpointRestore();
} catch (Exception ex) {
throw new CheckpointException("检查点创建失败", ex);
}
// 3. 检查点完成后的回调
afterCheckpoint();
}
private void beforeCheckpoint() {
// 通知所有注册的 @Checkpoint 监听器
// 关闭非必要线程(如健康检查定时器)
// 断开数据库连接(重新打开时恢复)
// 关闭文件句柄
}
private void afterCheckpoint() {
// 检查点已写入磁盘
// JVM 进程可以安全退出
}
}完整检查点流程:
CheckpointController.checkpoint()
↓
① 触发 @Checkpoint 回调(按 @Order 排序)
├── 关闭健康检查
├── 关闭连接池(数据库、Redis)
├── 关闭文件流
├── 关闭定时任务
└── 清理临时状态
↓
② org.crac.Core.checkpointRestore()
├── 冻结 JVM 线程
├── 序列化堆到磁盘
├── 保存线程状态
└── 创建检查点快照
↓
③ JVM 进程退出(可选)7. CRaC @Restore 恢复流程
从检查点恢复时,JVM 从磁盘读取检查点快照,重建完整的运行时状态。
// Spring Boot CRaC 自动配置
@AutoConfiguration
public class CRaCAutoConfiguration {
@Bean
public CRaCHandler cracHandler() {
return new CRaCHandler();
}
}
public class CRaCHandler implements Resource {
@Override
public void beforeCheckpoint(Context context) throws Exception {
// 由 CRaC 框架回调
}
@Override
public void afterRestore(Context context) throws Exception {
// 恢复后重新初始化资源
// 重新连接数据库
// 重新连接 Redis
// 重新启动定时任务
}
}恢复流程:
JVM 启动 → 检测到检查点文件
↓
① org.crac.Core.checkpointRestore() 恢复
├── 从磁盘加载堆快照
├── 恢复对象状态
├── 重建线程
└── 重建文件描述符
↓
② 触发 @Restore 回调
├── 重新连接数据库(连接池重建)
├── 重新连接 Redis
├── 重启定时任务
├── 重新打开日志文件
└── 通知外部服务(注册中心、配置中心)
↓
③ 应用恢复运行
├── Web 服务器恢复监听
└── 开始接受请求使用 @Checkpoint / @Restore 注解:
@Component
public class AppLifecycle {
// 检查点之前执行
@Checkpoint
public void onCheckpoint(Core.CheckpointContext context) {
log.info("准备创建检查点,清理资源...");
connectionPool.close();
healthCheckTimer.cancel();
}
// 恢复之后执行
@Restore
public void onRestore(Core.RestoreContext context) {
log.info("从检查点恢复,重新初始化资源...");
connectionPool.reconnect();
startHealthCheck();
serviceRegistry.register();
}
}CRaC 资源清单:
| 资源 | 检查点前 | 恢复后 |
|---|---|---|
| 数据库连接池 | 关闭 | 重新连接 |
| Redis 连接 | 关闭 | 重新连接 |
| 定时任务 | 暂停 | 重启 |
| 文件句柄 | 关闭/刷新 | 重新打开 |
| 日志系统 | 刷新缓冲 | 重新打开文件 |
| Web 服务器 | 暂停接受请求 | 恢复监听 |
| 注册中心 | 注销 | 重新注册 |
8. spring.context.checkpoint=on-refresh 的配置
Spring Boot 支持在 refresh() 完成后自动触发 CRaC 检查点。
# application.properties
spring.context.checkpoint=on-refresh配置背后的实现:
// SpringApplication 中处理 checkpoint
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// ... 完整启动流程
refreshContext(context);
afterRefresh(context, args);
// 检查是否配置了 on-refresh checkpoint
if (isCheckpointOnRefresh()) {
// 发布 ApplicationReadyEvent 后自动触发 checkpoint
CheckpointController controller = context.getBean(CheckpointController.class);
controller.checkpoint();
}
// ...
}
private boolean isCheckpointOnRefresh() {
// 检查 spring.context.checkpoint=on-refresh
return "on-refresh".equals(environment.getProperty("spring.context.checkpoint"));
}
}配置值的含义:
| 值 | 行为 |
|---|---|
on-refresh | 在 Spring 上下文 refresh 完成后自动触发 checkpoint |
| 未设置(默认) | 不自动触发,由外部触发(如发送 SIGUSR2 信号) |
外部触发的 CRaC 检查点:
# 方式 1:通过信号触发检查点
kill -SIGUSR2 <pid>
# 方式 2:通过 JMX 触发
# jconsole → com.org.crac.core → checkpoint()
# 方式 3:通过 Actuator 端点(如果自定义暴露)
# POST /actuator/checkpointspring-boot-starter-crac 自动配置的关键 Bean:
@AutoConfiguration
public class CRaCAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CheckpointController checkpointController() {
return new CheckpointController();
}
@Bean
@ConditionalOnMissingBean
public CRaCHandler cracHandler(ApplicationContext context) {
// 注册 Spring 上下文的 CRaC 支持
return new CRaCHandler(context);
}
}启动时间对比:
常规启动: ~3-5 秒(JVM 预热 + 类加载 + Bean 创建)
从检查点恢复: ~200-500ms(直接加载堆快照)总结
Virtual Threads 与 CRaC 的 8 个细节点总结如下:
| # | 细节点 | 核心类/机制 |
|---|---|---|
| ① | TomcatProtocolHandlerCustomizer 配置 Virtual Threads | protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor()) |
| ② | spring.threads.virtual.enabled=true 快捷开关 | Spring Boot 3.2+ 通过 @ConditionalOnProperty 自动配置 |
| ③ | ThreadPerTaskExecutor vs VirtualThreadExecutor | 后者使用 Thread.ofVirtual().factory() |
| ④ | Virtual Threads 下 Tomcat 的 maxThreads 参数无效 | server.tomcat.threads.max 在虚拟线程模式下被忽略 |
| ⑤ | Virtual Threads + @Async 的配合 | AsyncConfigurerCustomizer 自定义异步执行器 |
| ⑥ | CRaC CheckpointController.checkpoint() 流程 | 触发 @Checkpoint 回调 → 序列化堆 → 关闭非必要线程 → 存盘 |
| ⑦ | CRaC @Restore 恢复流程 | 反序列化堆 → 重建线程 → 执行 @Restore 回调 |
| ⑧ | spring.context.checkpoint=on-refresh 的配置 | 在 refresh() 完成后自动 checkpoint |