JAVA JVM虚拟线程
Java 虚拟线程是 Java 19 中引入的一项重要特性,并在 Java 21 中正式成为标准功能。它是 Java 平台对轻量级线程的实现,旨在简化高并发应用的开发,提升系统资源利用率。
传统线程的局限性
在虚拟线程出现之前,Java 依赖操作系统线程(OS Threads)实现并发,这些线程被称为平台线程(Platform Threads)。每个平台线程对应一个操作系统线程,具有以下特点:
- 重量级:创建和销毁成本高,内存占用大(通常 1MB + 栈空间)。
- 数量受限:系统资源限制了可创建的线程数量(如数千到数万级别)。
- 阻塞代价高:线程阻塞时会占用系统资源,导致上下文切换开销。
这使得传统多线程应用在处理大量并发连接(如 Web 服务器)时面临性能瓶颈。
虚拟线程的优势
虚拟线程是由 Java 运行时(JVM)管理的轻量级线程,具有以下特性:
1.轻量级
每个虚拟线程占用内存极小(约 KB 级别)。
创建和销毁成本低,可轻松创建百万级虚拟线程。
2.高效利用资源
虚拟线程由 JVM 调度,与操作系统线程解耦。
当虚拟线程阻塞时,JVM 会自动切换到其他任务,避免资源浪费。
3.同步代码简化异步编程
虚拟线程允许使用传统的synchronized、wait/notify等同步机制,无需编写复杂的异步回调代码。
虚拟线程核心概念
1.载体线程
虚拟线程运行在平台线程(称为载体线程)上,但多个虚拟线程可复用同一个载体线程。
载体线程由 JVM 的调度器管理,类似于操作系统调度进程。
2.协程(对应源码中Continuation类)
虚拟线程本质上是 Java 对协程的一种实现的官方支持,属于用户态线程。
什么是协程(补充):
协程(Coroutine) 是一种轻量级的线程管理机制,属于用户态线程(而非操作系统内核管理的线程)。它允许程序在不使用传统多线程的情况下实现并发执行
- 核心概念:
(1)协作式调度
协程与传统线程的最大区别在于调度方式:
传统线程:由操作系统内核调度,线程的挂起和恢复完全由系统控制(抢占式调度)。
协程:由程序自身控制执行流程,通过主动让出执行权(yield)来实现协作式调度。
(2)轻量级
协程的创建和销毁成本极低,内存占用通常只有 KB 级别(相比之下,传统线程的栈内存默认 1MB+)。因此,一个程序可以轻松创建数百万个协程。
(3)状态保存
协程能在暂停执行时保存自身状态(如局部变量、程序计数器),并在恢复时继续执行,就像从未中断过一样。
3.结构化并发
协程能在暂停执行时保存自身状态(如局部变量、程序计数器),并在恢复时继续执行,就像从未中断过一样。
从源码角度讲解虚拟线程的原理
1.虚拟线程的创建过程:
public final class VirtualThread extends Thread {private final Continuation cont; // 协程上下文private final Executor scheduler; // 任务调度器public VirtualThread(Runnable target) {super(null, null, "VirtualThread", 0, false, null);// 创建Continuation实例,传入执行逻辑this.cont = new Continuation(VM_VTHREAD_CONTINUATION_STACK, target);this.scheduler = VirtualThread.captureScheduler();}@Overridepublic void start() {if (getState() != Thread.State.NEW)throw new IllegalThreadStateException();// 将虚拟线程提交给调度器scheduler.execute(this::runContinuation);}private void runContinuation() {// 执行Continuation(协程)cont.run();}
}
关键点:
- Continuation是虚拟线程的核心,它封装了线程的执行上下文(如局部变量、程序计数器)。
- start()方法不会立即执行线程,而是将其放入调度器的任务队列。
2.Continuation 机制详解
Continuation是 JVM 提供的底层协程实现
// jdk.internal.vm.Continuation简化
public final class Continuation {private final Object stack; // 协程栈数据private final Runnable task; // 待执行的任务private ContinuationScope scope; // 协程作用域public Continuation(ContinuationScope scope, Runnable target) {this.scope = scope;this.task = target;this.stack = allocateStack(); // 分配协程栈(小而动态)}public void run() {if (status == INITIAL) {// 首次执行,进入JVM层的协程执行逻辑VM.continuationStart(this);} else if (status == SUSPENDED) {// 恢复执行VM.continuationResume(this);}}// 暂停当前协程的执行public static void yield() {VM.continuationYield(currentContinuation());}
}
JVM 层面的关键机制:
- VM.continuationStart():启动协程执行,保存初始上下文。
- VM.continuationResume():恢复协程执行,从上次暂停处继续。
- VM.continuationYield():暂停协程执行,保存当前上下文并释放载体线程。
3. 调度器与载体线程
虚拟线程的调度由ForkJoinPool实现,核心逻辑如下:
// ForkJoinPool虚拟线程调度器的简化逻辑
class ForkJoinPool implements ExecutorService {// 工作线程数组(载体线程)private final ForkJoinWorkerThread[] workers;// 提交虚拟线程任务public void execute(Runnable task) {if (task instanceof VirtualThread) {VirtualThread vt = (VirtualThread) task;// 将任务放入工作队列WorkQueue queue = getWorkerQueue();queue.push(vt);// 唤醒或创建载体线程执行任务signalWork();}}// 载体线程执行逻辑final void runWorker(WorkQueue w) {while (scan(w)) { // 扫描任务Runnable task = w.pop();if (task instanceof VirtualThread) {VirtualThread vt = (VirtualThread) task;// 执行虚拟线程的Continuationvt.runContinuation();}}}
}
调度关键点:
- Work-Stealing 算法:每个载体线程维护自己的任务队列,空闲时从其他队列 "窃取" 任务。
- 载体线程池:默认大小为 CPU 核心数,可通过jdk.virtualThreadScheduler.parallelism参数调整。
- 弹性伸缩:调度器会根据负载动态调整活跃载体线程数量。
4. 阻塞操作的处理
当虚拟线程遇到阻塞操作(如 IO)时,JDK 会通过 Instrumentation 机制自动转换为非阻塞逻辑
步骤:
- 检测当前线程是否为虚拟线程。
- 将阻塞操作转换为非阻塞调用(如使用 NIO Selector)。
- 在 IO 等待期间,通过Continuation.yield()暂停虚拟线程。
- IO 完成后,调度器恢复虚拟线程执行。
从源码角度对传统线程与虚拟线程进行对比分析
1. 线程创建与资源占用
平台线程(Thread类)Java 平台线程直接映射到操作系统线程,由java.lang.Thread类实现。
核心源码结构
// java.lang.Thread类简化结构
public class Thread implements Runnable {private long nativePeer; // 指向JVM中对应的原生线程// 线程栈大小(默认由JVM决定,通常为1MB+)private long stackSize;public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}private void init(ThreadGroup g, Runnable target, String name, long stackSize) {// ...this.stackSize = stackSize;// 调用JVM底层创建原生线程start0();}private native void start0(); // 调用操作系统创建线程
}
关键点:
- nativePeer字段指向 JVM 内部的原生线程结构。
- 每个线程默认占用较大的栈空间(如 1MB),创建成本高。
- start0()是本地方法,依赖操作系统创建线程,开销大。
虚拟线程(VirtualThread类)虚拟线程由java.lang.VirtualThread实现,是 Java 21 + 的内置类:
// java.lang.VirtualThread类简化结构
public final class VirtualThread extends Thread {private final Continuation cont; // 协程实现的核心public VirtualThread(ThreadGroup group, Runnable target) {super(group, null, "VirtualThread", 0, false, null);// 创建协程上下文this.cont = new Continuation(VM_VTHREAD_CONTINUATION_STACK, target);}@Overridepublic void start() {// 将虚拟线程提交给调度器scheduler.submit(this);}
}
关键点:
- Continuation是 JVM 内部的协程实现,负责保存和恢复线程状态。
- 虚拟线程栈内存由 JVM 管理(通常 KB 级别),创建成本极低。
- start()方法仅将任务提交给调度器,不直接创建操作系统线程。
2. 调度机制对比
平台线程调度
平台线程的调度完全依赖操作系统:
- 线程状态(RUNNABLE、BLOCKED 等)由操作系统内核管理。
- 线程阻塞时(如 IO 操作),会触发上下文切换(从用户态到内核态),成本高(约 1μs~10μs)。
虚拟线程调度
虚拟线程由 JVM 用户态调度器管理:
// 虚拟线程调度器简化逻辑
class VirtualThreadScheduler {private final ExecutorService carrierThreads; // 载体线程池public void submit(VirtualThread vt) {// 将虚拟线程放入任务队列taskQueue.add(vt);// 唤醒载体线程执行任务carrierThreads.submit(() -> runTask(vt));}private void runTask(VirtualThread vt) {// 恢复虚拟线程上下文vt.cont.run();}
}
关键点:
- 虚拟线程阻塞时(如 IO),JVM 通过Continuation.yield()暂停执行,释放载体线程资源。
- 载体线程可继续执行其他虚拟线程,实现多路复用,大幅提升吞吐量。
- 调度器使用 Work-Stealing 算法优化任务分配。
3. 阻塞操作处理
平台线程的阻塞
平台线程执行阻塞操作时:
// 传统线程执行IO操作(阻塞示例)
public void readFile() throws IOException {try (var reader = new BufferedReader(new FileReader("data.txt"))) {String line;while ((line = reader.readLine()) != null) { // 阻塞调用// 处理数据}}
}
- 线程会被挂起,等待 IO 完成,期间占用系统资源。
虚拟线程的阻塞
虚拟线程执行相同操作时:
// 虚拟线程执行IO操作
public void readFile() throws IOException {try (var reader = new BufferedReader(new FileReader("data.txt"))) {String line;while ((line = reader.readLine()) != null) { // 虚拟线程在此处"暂停"// 处理数据}} // 虚拟线程恢复执行
}
- JVM 会将虚拟线程标记为阻塞,暂停执行并释放载体线程。
- IO 完成后,虚拟线程会在任意可用的载体线程上恢复执行。
4. 适用场景对比
场景 | 平台线程 | 虚拟线程 |
IO 密集型高并发 | 需要复杂的异步编程 | 直接使用同步代码即可 |
CPU 密集型任务 | 合适(避免频繁切换) | 不合适(调度开销可能更高) |
资源受限环境 | 需谨慎控制线程数 | 可大量创建,自动优化资源 |
代码迁移成本 | 需重构为异步(如 CompletableFuture) | 直接替换线程池即可 |
优劣分析
平台线程的优势
- 适合 CPU 密集型任务:避免频繁调度开销。
- 实时性强:由操作系统内核直接调度,响应速度快。
- 兼容性好:支持所有 Java 版本,无需特殊 API。
平台线程的劣势
- 资源消耗大:高并发时内存占用显著。
- 扩展性差:受限于操作系统线程数量上限。
- 编程模型复杂:处理大量并发需使用异步编程(如 Netty)。
虚拟线程的优势
- 高扩展性:轻松支持百万级并发连接。
- 简化编程:使用同步代码实现异步性能。
- 资源高效:内存占用低,上下文切换快。
- 无缝兼容:与现有线程 API(如ExecutorService)兼容。
虚拟线程的劣势
- JDK 版本依赖:仅 Java 21 + 支持。
- 调试复杂度:虚拟线程堆栈信息可能更复杂。
- CPU 密集型优化不足:长时间运行的任务可能导致调度器过载。
虚拟线程适用场景
- 高并发服务器:如 Web 服务器和应用服务器,处理大量并发请求时性能卓越。
- 微服务架构:使服务更轻量级、敏捷,提升服务间通信效率。
- 并行计算:在数据处理和分析场景中,提供必要并行处理能力,提升计算效率。
- 异步 I/O 操作:涉及文件或网络 I/O 的应用程序中,可提高异步操作性能。
总结
虚拟线程的高性能源于以下设计:
1.轻量级上下文切换
虚拟线程切换仅涉及用户态数据(Continuation 对象),无需内核参与。
2.栈内存优化
虚拟线程使用动态增长的小栈(通常 KB 级别),而不是固定的大栈。
3.无锁设计
调度器使用 CAS 操作和无锁队列减少竞争。
4.任务窃取
Work-Stealing 算法确保负载均衡,减少线程空闲时间。
虚拟线程本质上是:
- 用户态线程:由 JVM 而非操作系统管理。
- 协作式调度:通过Continuation.yield()主动让出执行权。
- 轻量级协程:每个线程占用极小资源,支持百万级并发。
Java 虚拟线程通过轻量级设计和高效调度,显著降低了并发编程的资源成本,使开发者可以用同步代码编写高性能异步应用。它是 Java 平台应对现代高并发场景的重要改进,尤其适合 IO 密集型的大规模并发应用。