虚拟线程通过JVM调度器在阻塞点自动挂载/卸载到载体线程,不绑定物理线程;仅在I/O、sleep、wait、锁竞争等阻塞操作时触发迁移,依赖Continuation机制保存恢复栈状态,纯计算任务则持续运行于同一载体线程。

载体线程不是虚拟线程的“宿主”,而是它执行时临时借来的平台线程。虚拟线程本身不绑定任何物理线程,它的迁移不是主动切换,而是由JVM在阻塞点自动完成的挂载(mount)与卸载(unmount)过程。
迁移发生的触发条件
虚拟线程只在特定操作发生时才会从当前载体线程上卸载,并可能在之后挂载到另一个载体线程上:
-
I/O 阻塞调用:如
Socket.read()、FileChannel.read()、数据库 JDBC 查询(非异步驱动) -
显式休眠或等待:如
Thread.sleep()、Object.wait()、LockSupport.park() - synchronized 块内竞争失败:当尝试进入已被占用的同步块,且无法立即获取锁时
- 某些本地方法或 JNI 调用:若底层实现会阻塞 OS 线程,JVM 也会触发卸载
迁移过程的本质是状态保存与恢复
迁移不是复制线程,而是延续(Continuation)机制在起作用:
- 卸载时,JVM 将当前虚拟线程的完整 Java 栈(包括所有局部变量、程序计数器、调用链)序列化为一个 Continuation 对象,存入堆内存
- 载体线程被立即释放,去执行其他就绪的虚拟线程任务
- I/O 完成或超时结束后,JVM 从队列中取出该 Continuation,选择一个空闲的载体线程,将其挂载上去
- 挂载后,程序从阻塞语句的下一行继续执行,调用栈完全一致,对业务代码完全透明
载体线程池是动态管理的
JVM 内部维护一个平台线程池(默认基于 ForkJoinPool.commonPool() 或专用 carrier pool),其规模通常远小于虚拟线程数量:
- 初始只有少量活跃载体线程(如 CPU 核心数 × 2)
- 当并发阻塞任务激增,现有载体线程全部忙于 I/O 回调或计算时,JVM 可按需创建新载体线程(但有上限,避免 OS 资源耗尽)
- 空闲一段时间后,多余载体线程会被自动终止回收
- 你无法控制某虚拟线程“固定”运行在哪一个载体线程上——这是 JVM 调度器的职责,也是轻量级的关键所在
不会迁移的场景:纯计算型执行
如果一段代码全程无阻塞操作(比如密集循环、数学运算、内存遍历),虚拟线程会持续运行在同一个载体线程上,直到它自然结束或被更高优先级任务抢占(此时仍是平台线程级别的抢占,与虚拟线程无关)。这种情况下,它和传统线程的行为几乎一致,只是栈更小、创建更快。

















