前言
最近看到很多小伙伴的疑惑,都是同一个问题:
"我项目还在用JDK 8,要不要升级?"
"JDK 17和JDK 21有什么区别?"
"升级会不会有兼容性问题?"
"虚拟线程到底是什么?怎么用?"
今天这篇文章,我用一整篇的篇幅,把Java版本演进、JDK 17/21的核心特性、升级注意事项,一次性讲清楚。
郑重声明: 本文内容客观中立,不涉及任何政治立场,纯粹从技术角度分析Java版本演进。
一、Java版本演进史:一图看懂8年巨变
1.1 版本时间线
┌─────────────────────────────────────────────────────────────────────────────┐
│ Java版本演进时间线 │
│ │
│ 2014年 2018年 2021年 2023年 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │JDK 8 │ │JDK 11│ │JDK 17│ │JDK 21│ │
│ │ LTS │ │ LTS │ │ LTS │ │ LTS │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │ │
│ │ ┌─────┴─────┐ │ ┌─────┴─────┐ │
│ │ │ JDK 9-16 │ │ │ JDK 22-24 │ │
│ │ │ (过渡版本) │ │ │ (过渡版本) │ │
│ │ └───────────┘ │ └───────────┘ │
│ │ │ │
│ └─────────────────────────┴─────────────────────────────────────────────►│
│ │
│ 2018年9月后:Oracle调整发布策略,每6个月发布一个新版本 │
│ 每两年发布一个LTS(长期支持)版本 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
1.2 LTS版本一览
| 版本 | 发布时间 | 停止免费支持 | 生命周期 | 企业定位 |
|---|---|---|---|---|
| JDK 8 | 2014年3月 | 2025年3月 | ~11年 | 经典遗留,企业大量使用 |
| JDK 11 | 2018年9月 | 2026年9月 | ~8年 | 过渡版本,新增模块系统 |
| JDK 17 | 2021年9月 | 2029年9月 | ~8年 | 当前主流LTS,稳定可靠 |
| JDK 21 | 2023年9月 | 2031年9月 | ~8年 | 最新LTS,虚拟线程元年 |
1.3 为什么要升级?
先看一组数据对比:
| 对比项 | JDK 8 | JDK 17 | JDK 21 | 提升幅度 |
|---|---|---|---|---|
| GC暂停时间 | 数十~数百ms | <10ms (ZGC) | <1ms (ZGC) | 50-100倍 |
| 启动时间 | 基准 | -20% | -30% | 30%+ |
| 内存占用 | 基准 | -10% | -15% | 15%+ |
| 新语法特性 | 基础 | 完整模式匹配 | 完整模式匹配+Record | 质变 |
| 虚拟线程 | ❌ 不支持 | ❌ 不支持 | ✅ 原生支持 | 并发革命 |
残酷的现实:
如果你还在用JDK 8,就像2025年还在用塞班系统。能跑是能跑,但时代变了。
二、JDK 17深度解析:LTS版本的诚意之作
JDK 17是2021年9月发布的LTS版本,被Oracle称为"最具生产力的LTS"。它是自JDK 8以来改动最大的LTS版本。
2.1 JDK 17 新特性全景图
┌─────────────────────────────────────────────────────────────────────────────┐
│ JDK 17 新特性全景 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 语言层面增强 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
│ │ │ 密封类正式版 │ │ Switch模式 │ │ instanceof │ │ Record │ │ │
│ │ │ Sealed │ │ 匹配增强 │ │ 模式匹配 │ │ 数据类 │ │ │
│ │ │ Classes │ │ │ │ │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 核心库升级 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
│ │ │ 伪随机数增强 │ │ 外部函数内存 │ │ Vector │ │ 文本块 │ │ │
│ │ │ PRG │ │ API │ │ API │ │ Text Block│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 性能与安全 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 强封装 │ │ 移除Applet │ │ RMI 弃用 │ │ │
│ │ │ 内部API │ │ │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 密封类(Sealed Classes):控制继承的终极武器
密封类是JDK 17最重磅的语言特性之一,它允许你精确控制哪些类可以继承你的类。
没有密封类之前的问题:
// 定义了一个形状接口
public interface Shape {
double area();
}
// 你认为只有这三个实现类
class Circle implements Shape { }
class Rectangle implements Shape { }
class Triangle implements Shape { }
// 但别人可以偷偷加一个奇怪的实现
class Unicorn implements Shape { } // 独角兽也是形状?
使用密封类:
// sealed:密封类
// permits:只允许这些类继承
public sealed class Shape permits Circle, Rectangle, Triangle {
// 公共逻辑
public void print() {
System.out.println("这是一个形状");
}
}
// Circle:final,表示不能再被继承
public final class Circle extends Shape {
private double radius;
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// Rectangle:final
public final class Rectangle extends Shape {
private double width, height;
@Override
public double area() {
return width * height;
}
}
// Triangle:final
public final class Triangle extends Shape {
private double base, height;
@Override
public double area() {
return 0.5 * base * height;
}
}
// ❌ 编译错误!Unicorn不在允许列表中
// class Unicorn extends Shape { } // 编译器报错!
密封类的三种子类修饰符:
public sealed class Person permits Employee, Manager, Boss {
}
// 1. final:不能再被继承(最常用)
final class Employee extends Person { }
// 2. sealed:允许继续密封
sealed class Manager extends Person permits SeniorManager {
// Manager只允许SeniorManager继承
}
final class SeniorManager extends Manager { }
// 3. non-sealed:开放继承
non-sealed class Boss extends Person {
// Boss可以被任何人继承
}
class CEO extends Boss { } // 合法
在switch中使用密封类(完美类型检查):
public sealed class Shape permits Circle, Rectangle, Triangle { }
// 编译器知道你只有这三个子类,可以穷尽检查
public static String getShapeName(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,半径=" + c.getRadius();
case Rectangle r -> "矩形,宽=" + r.getWidth() + ",高=" + r.getHeight();
case Triangle t -> "三角形,底=" + t.getBase() + ",高=" + t.getHeight();
// ✅ 编译器知道不需要default,因为所有情况都已覆盖
};
}
应用场景:
// 1. API设计:精确控制扩展点
public sealed interface Plugin permits DatabasePlugin, CachePlugin, HttpPlugin {
void execute();
}
// 2. 领域驱动设计:限定实体类型
public sealed interface DomainEvent permits OrderCreatedEvent, OrderPaidEvent, OrderCancelledEvent {
Instant occurredAt();
}
// 3. 策略模式:限制策略实现
public sealed interface DiscountStrategy permits PercentageDiscount, FixedDiscount, NoDiscount {
BigDecimal apply(BigDecimal originalPrice);
}
2.3 Pattern Matching for instanceof:告别强制类型转换
JDK 16之前的写法(繁琐):
Object obj = getData();
// 一行代码,三种写法
if (obj instanceof String) {
String str = (String) obj; // 先强制转换
System.out.println(str.length()); // 再使用
}
// 或者用强制转换
String str = (String) obj;
System.out.println(str.length());
JDK 16+的写法(简洁):
Object obj = getData();
// 模式匹配:直接在if中使用变量
if (obj instanceof String str) {
// str在这里直接可用,无需强制转换
System.out.println(str.length());
System.out.println(str.toUpperCase()); // 可以直接调用String方法
}
// 还可以加条件
if (obj instanceof String str && str.length() > 5) {
System.out.println("字符串长度大于5:" + str);
}
复杂的嵌套模式:
// 记录类(在JDK 16中正式发布)
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
public static void describe(Object obj) {
// 嵌套模式匹配
if (obj instanceof Circle(Point(int x, int y), int r)) {
System.out.println("圆心:(" + x + ", " + y + "),半径:" + r);
}
}
2.4 Switch表达式增强:更强大的模式匹配
JDK 12-14预览,JDK 17正式版
基础Switch表达式:
// JDK 8:传统Switch(只有语句)
switch (day) {
case MONDAY:
case FRIDAY:
System.out.println("工作日");
break;
case SATURDAY:
case SUNDAY:
System.out.println("周末");
break;
}
// JDK 17:Switch表达式(可以返回值)
String result = switch (day) {
case MONDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
default -> "未知";
};
箭头函数与冒号的区别:
// 箭头函数:简洁,不穿透
case MONDAY -> System.out.println("周一");
// 冒号:需要break,穿透执行
case MONDAY:
System.out.println("周一");
break; // 必须加break,否则会穿透
带条件判断的Switch:
public static String describe(int number) {
return switch (number) {
case 1, 2, 3 -> "小数字";
case 4, 5, 6 -> "中数字";
case 7, 8, 9, 10 -> "大数字";
default -> {
if (number < 0) {
yield "负数";
} else {
yield "超大的数字";
}
}
};
}
2.5 Record类:不可变数据的最佳实践
为什么需要Record?
Java中定义数据类是个繁琐的工作:
// 传统POJO:几十行代码起步
public class Person {
private final String name;
private final int age;
private final String email;
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// getter
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
// equals/hashCode/toString
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}
Record:一行顶几十行
// 一行定义,自动生成:构造方法、getter、equals、hashCode、toString
public record Person(String name, int age, String email) {}
// 自动生成的方法:
// 1. 构造方法:public Person(String name, int age, String email)
// 2. getter:public String name(), public int age(), public String email()
// 3. equals:基于所有属性
// 4. hashCode:基于所有属性
// 5. toString:包含所有属性
// 使用
Person person = new Person("张三", 25, "zhangsan@example.com");
System.out.println(person.name()); // 张三(注意:不是getName())
System.out.println(person.age()); // 25
System.out.println(person.toString()); // Person[name=张三, age=25, email=zhangsan@example.com]
Record的高级用法:
// 1. 自定义构造方法(验证)
public record Age(int value) {
// 紧凑构造方法
public Age {
if (value < 0 || value > 150) {
throw new IllegalArgumentException("年龄不合法:" + value);
}
}
}
// 2. 添加额外方法
public record Point(int x, int y) {
// 静态工厂方法
public static Point origin() {
return new Point(0, 0);
}
// 实例方法
public double distanceFromOrigin() {
return Math.sqrt(x * x + y * y);
}
}
// 3. Record实现接口
public record SerializablePoint(int x, int y) implements Serializable {
// 实现序列化接口
}
// 4. 本地Record(方法内部使用)
public void process(List<Person> people) {
// 定义只在方法内部使用的record
record PersonInfo(String name, int age) {}
List<PersonInfo> infos = people.stream()
.map(p -> new PersonInfo(p.name(), p.age()))
.toList();
}
2.6 文本块(Text Blocks):告别字符串拼接噩梦
传统字符串的痛:
// JSON字符串
String json = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 25,\n" +
" \"skills\": [\n" +
" \"Java\",\n" +
" \"Python\"\n" +
" ]\n" +
"}";
// SQL字符串
String sql = "SELECT id, name, email " +
"FROM users " +
"WHERE age > 18 " +
"ORDER BY name";
文本块的优雅写法:
// JSON字符串:三个双引号开始,三个双引号结束
String json = """
{
"name": "张三",
"age": 25,
"skills": [
"Java",
"Python"
]
}
""";
// SQL字符串
String sql = """
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name
""";
// HTML字符串
String html = """
<!DOCTYPE html>
<html>
<head>
<title>用户信息</title>
</head>
<body>
<h1>欢迎,""" + username + """</h1>
</body>
</html>
""";
文本块的格式化控制:
// \s 表示空格(保留行末空格)
String formatted = """
第一行
第二行\s\s\s
第三行
""";
// 去除前导缩进:使用stripIndent()
String code = """
public class Example {
public static void main(String[] args) {
System.out.println("Hello");
}
}
""".stripIndent();
2.7 伪随机数生成器增强
// JDK 17新增了多种随机数生成器
public class RandomGeneratorDemo {
public static void main(String[] args) {
// 1. LXM系列:推荐使用
RandomGenerator generator = RandomGenerator.of("L64X128MixRandom");
// 2. 专门针对特定场景的生成器
// SplittableRandom:分叉随机数,适合并行
SplittableRandom splittable = new SplittableRandom();
// 3. 新的工厂方法
RandomGenerator random = RandomGenerator.getDefault();
// 生成各种类型的随机数
random.ints().limit(5).forEach(System.out::println);
random.longs().limit(5).forEach(System.out::println);
random.doubles().limit(5).forEach(System.out::println);
}
}
三、JDK 21深度解析:虚拟线程引领并发革命
JDK 21是2023年9月发布的LTS版本,被称为"Java有史以来最重要的更新"之一。虚拟线程的正式发布,让Java并发编程进入了新时代。
3.1 JDK 21 新特性一览
┌─────────────────────────────────────────────────────────────────────────────┐
│ JDK 21 新特性全景 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 🌟 核心重磅特性 │ │
│ │ │ │
│ │ ┌───────────────────┐ ┌───────────────────┐ ┌─────────────────┐ │ │
│ │ │ 虚拟线程正式版 │ │ Unnamed Patterns │ │ String Templates│ │ │
│ │ │ Virtual Threads │ │ 未知模式匹配 │ │ 字符串模板 │ │ │
│ │ │ JEP 444 │ │ JEP 456 │ │ JEP 459 │ │ │
│ │ └───────────────────┘ └───────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 语言增强 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Switch模式 │ │ Record模式 │ │ Class文件 │ │ │
│ │ │ 匹配全面 │ │ 增强 │ │ 新代版本 │ │ │
│ │ │ JEP 441 │ │ JEP 440 │ │ JEP 457 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 库改进 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 核心模块 │ │ 流式API │ │ Foreign │ │ │
│ │ │ 新一代ZGC │ │ 增强 │ │ Function&Mem │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 虚拟线程(Virtual Threads):并发编程的革命
这是JDK 21最重磅的特性,没有之一。
3.2.1 为什么需要虚拟线程?
传统线程的问题:
// 传统的Java线程是由操作系统管理的
// 每个线程占用约1MB栈空间
Thread thread = new Thread(() -> {
// 线程栈:默认1MB
doWork();
});
thread.start();
// 10万个并发请求 = 10万个线程 = 100GB内存!
// 实际上服务器根本承受不了
传统BIO的阻塞问题:
// 假设这是一个HTTP服务器
public class OldServer {
// 线程池最多100个线程
private ExecutorService executor = Executors.newFixedThreadPool(100);
public void handleRequest(Request request) {
executor.execute(() -> {
// 假设数据库查询需要100ms
Result result = database.query(request.getSql());
// 假设HTTP调用需要200ms
Result httpResult = httpClient.call(request.getUrl());
// 这个线程在等待的300ms内什么都做不了
// 但它占着1MB内存
response.send(result, httpResult);
});
}
}
问题总结:
┌─────────────────────────────────────────────────────────────────┐
│ 传统线程的困境 │
│ │
│ 场景:10000个并发请求,每个请求阻塞300ms(IO等待) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 线程池100个线程 │ │
│ │ │ │
│ │ 线程1: [等待IO ]░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │
│ │ 线程2: [等待IO ]░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │
│ │ 线程3: [工作中 ]███████████████████████ │ │
│ │ ... │ │
│ │ 线程100: [等待IO ]░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │
│ │ │ │
│ │ 问题:9900个请求在队列等待,因为没有线程处理! │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 传统方案:用异步编程(CompletableFuture/RxJava) │
│ 问题:代码复杂,调试困难,思维模式跳跃 │
└─────────────────────────────────────────────────────────────────┘
3.2.2 虚拟线程原理:M:N调度模型
概念对比:
┌─────────────────────────────────────────────────────────────────────┐
│ 线程模型对比 │
│ │
│ 平台线程(传统) │ 虚拟线程(Java 21) │
│ │ │
│ 1:1 模型 │ M:N 模型 │
│ ┌─────────────┐ │ ┌─────────────┐ │
│ │ 用户代码 │ │ │ 用户代码 │ │
│ │ (Runnable)│ │ │ (Runnable)│ │
│ └──────┬──────┘ │ └──────┬──────┘ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌─────────────┐ │ ┌─────────────┐ │
│ │ 平台线程 │ │ │ 虚拟线程 │ │
│ │(OS管理,1MB) │ │ │(JVM管理,~KB)│ │
│ └──────┬──────┘ │ └──────┬──────┘ │
│ │ │ │ │
│ ▼ │ ┌───────┴───────┐ │
│ ┌─────────────┐ │ ▼ ▼ │
│ │ 操作系统 │ │ ┌─────────┐ ┌─────────┐ │
│ │ (内核线程) │ │ │ 载体线程 │ │ 载体线程 │ │
│ └─────────────┘ │ │ (平台) │ │ (平台) │ │
│ │ └────┬────┘ └────┬────┘ │
│ 10000请求=10000线程 │ │ │ │
│ =10000×1MB=10GB │ └───────┬───────┘ │
│ │ ▼ │
│ │ ┌─────────────┐ │
│ │ │ 操作系统 │ │
│ │ └─────────────┘ │
│ │ │
│ │ 10000虚拟线程 = ~100载体线程 │
│ │ 内存:100×1MB = 100MB(减少99%) │
└─────────────────────────────────────────────────────────────────────┘
虚拟线程的生命周期:
┌─────────────────────────────────────────────────────────────────────┐
│ 虚拟线程生命周期 │
│ │
│ mount(挂载)→ 运行 → yield(让出)→ 阻塞 → unmount(卸载) │
│ │
│ 1. mount:把虚拟线程的栈帧拷贝到载体线程 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 虚拟线程栈 │ copy │ 载体线程栈 │ │
│ │ [frame1] │ ──────► │ [frame1] │ │
│ │ [frame2] │ │ [frame2] │ │
│ │ [frame3] │ │ [frame3] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 2. 运行:虚拟线程在载体线程上执行 │
│ while (hasWork) { │
│ executeFrame(); │
│ if (blocking) yield(); // 遇到阻塞,yield │
│ } │
│ │
│ 3. yield(让出):遇到阻塞操作时 │
│ - 栈帧拷贝回堆内存 │
│ - 载体线程被释放 │
│ - 虚拟线程被挂起 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 虚拟线程栈 │ save │ 载体线程栈 │ (释放) │
│ │ [frame1] ✓ │ ◄───── │ [frame1] │ │
│ │ [frame2] ✓ │ │ [frame2] │ │
│ │ [frame3] │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 4. unmount(卸载):载体线程可以执行其他虚拟线程 │
│ ┌─────────────┐ │
│ │ 载体线程 │ → 执行虚拟线程B │
│ └─────────────┘ │
│ │
│ 5. 阻塞结束后:重新调度 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 虚拟线程栈 │ mount │ 载体线程栈 │ │
│ │ [frame1] │ ◄───── │ [frame1] │ │
│ │ [frame2] │ │ [frame2] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2.3 虚拟线程实战
创建虚拟线程的三种方式:
public class VirtualThreadDemo {
public static void main(String[] args) {
// 方式1:Thread.ofVirtual() 工厂方法(JDK 21)
Thread virtualThread1 = Thread.ofVirtual().name("vt-1").start(() -> {
System.out.println("虚拟线程运行中:" + Thread.currentThread());
});
// 方式2:Thread.startVirtualThread()(最简单)
Thread.startVirtualThread(() -> {
System.out.println("快速启动虚拟线程");
});
// 方式3:Executors.newVirtualThreadPerTaskExecutor()
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> future = executor.submit(() -> "Hello Virtual Thread!");
System.out.println(future.get());
}
// 等待虚拟线程完成
try {
virtualThread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
虚拟线程 vs 平台线程对比:
public class ThreadComparison {
public static void main(String[] args) throws Exception {
int taskCount = 100_000;
// 测试平台线程
System.out.println("===== 平台线程测试 =====");
long startPlatform = System.currentTimeMillis();
try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
try {
// 模拟IO阻塞
Thread.sleep(Duration.ofSeconds(1));
} catch (InterruptedException e) {}
latch.countDown();
});
}
latch.await();
}
long platformTime = System.currentTimeMillis() - startPlatform;
System.out.println("平台线程耗时:" + platformTime + "ms");
System.out.println("线程数:200(受限)");
System.out.println();
// 测试虚拟线程
System.out.println("===== 虚拟线程测试 =====");
long startVirtual = System.currentTimeMillis();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
try {
// 模拟IO阻塞
Thread.sleep(Duration.ofSeconds(1));
} catch (InterruptedException e) {}
latch.countDown();
});
}
latch.await();
}
long virtualTime = System.currentTimeMillis() - startVirtual;
System.out.println("虚拟线程耗时:" + virtualTime + "ms");
System.out.println("线程数:100000(每个任务一个虚拟线程)");
}
}
输出结果(理论值):
===== 平台线程测试 =====
平台线程耗时:5000ms(约)
线程数:200(受限)
===== 虚拟线程测试 =====
虚拟线程耗时:1000ms(约)
线程数:100000(每个任务一个虚拟线程)
为什么虚拟线程快这么多?
┌─────────────────────────────────────────────────────────────────────┐
│ 性能对比图 │
│ │
│ 任务数:100000,每个阻塞1秒 │
│ │
│ 平台线程(200个): │
│ 时间轴 ─────────────────────────────────────────────────────► │
│ ████████████████████████████████████████████ │
│ (100000 / 200) / 1s ≈ 500秒 → 但实际是并发分批:5000ms │
│ │
│ 虚拟线程(100000个): │
│ 时间轴 ──────────────────────► │
│ ████████████ │
│ (100000) / 1s ≈ 1秒(几乎全部同时等待) │
│ │
│ 原理:虚拟线程遇到阻塞就yield,载体线程去跑其他虚拟线程 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2.4 虚拟线程的最佳实践
public class VirtualThreadBestPractices {
/**
* ✅ 正确:使用虚拟线程执行器
*/
public class GoodExample {
// HTTP服务:每个请求一个虚拟线程
public void httpServerExample() throws Exception {
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
// 模拟处理10000个HTTP请求
for (int i = 0; i < 10000; i++) {
final int requestId = i;
executor.submit(() -> {
// 每个请求占用一个虚拟线程
handleHttpRequest(requestId);
});
}
}
}
// 数据库访问
public void databaseAccessExample() {
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<Result>> futures = IntStream.range(0, 1000)
.mapToObj(i -> executor.submit(() -> queryDatabase(i)))
.toList();
futures.forEach(f -> {
try {
Result r = f.get();
process(r);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
/**
* ❌ 错误:不要在线程池中混合使用虚拟线程
*/
public class BadExample {
// 错误1:在虚拟线程中使用ThreadPoolExecutor
public void badMix1() {
// ❌ 不好:虚拟线程本身就是轻量的,不需要再池化
ExecutorService pool = Executors.newFixedThreadPool(10);
Thread.startVirtualThread(() -> {
pool.submit(() -> {}); // 这是在虚拟线程里用线程池
});
}
// 错误2:虚拟线程执行阻塞操作
public void badBlocking() {
ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
// ❌ 不好:虚拟线程遇到synchronized会pin住载体线程
synchronized (this) {
// 长时间持有锁
}
});
}
}
/**
* ⚠️ 注意事项:synchronized和虚拟线程
*/
public class SynchronizedWarning {
// JDK 21之前:synchronized会pin住载体线程
// JDK 21之后:JVM会尝试自动优化,尽量减少pin
// 如果确实需要长时间持有锁,建议使用ReentrantLock
public class BetterLocking {
private final ReentrantLock lock = new ReentrantLock();
public void doWork() {
lock.lock();
try {
// 使用ReentrantLock,允许虚拟线程yield
// 长时间操作
} finally {
lock.unlock();
}
}
}
}
}
3.2.5 虚拟线程的适用场景
| 场景 | 适用性 | 说明 |
|---|---|---|
| HTTP服务端 | ✅ 强烈推荐 | 每个请求一个虚拟线程,轻松处理百万并发 |
| 数据库访问 | ✅ 强烈推荐 | JDBC调用大多是IO等待 |
| 文件IO | ✅ 推荐 | NIO配合虚拟线程效果最佳 |
| 消息队列消费者 | ✅ 推荐 | 长时间监听消息 |
| CPU密集型任务 | ❌ 不推荐 | 虚拟线程不提升计算性能 |
| 批量计算 | ❌ 不推荐 | 使用并行流或平台线程 |
3.3 Switch模式匹配增强(JEP 441)
JDK 21完善了switch的模式匹配能力:
// JDK 17:switch表达式支持模式匹配
// JDK 21:更强大的模式匹配
public class PatternMatchingDemo {
// 1. 支持null的case
public static void printType(Object obj) {
switch (obj) {
case null -> System.out.println("是null");
case String s when s.isEmpty() -> System.out.println("空字符串");
case String s -> System.out.println("字符串:" + s);
case Integer i -> System.out.println("整数:" + i);
default -> System.out.println("其他类型");
}
}
// 2. record模式匹配
record Point(int x, int y) {}
public static String describe(Object obj) {
return switch (obj) {
// 嵌套record模式
case Point(int x, int y) when x == y ->
"对角线上的点(" + x + "," + y + ")";
case Point(int x, int y) ->
"普通点(" + x + "," + y + ")";
case Circle(Point center, int r) ->
"圆心在(" + center.x() + "," + center.y() + ")的圆";
default -> "其他形状";
};
}
}
3.4 Record模式增强(JEP 440)
record Point(int x, int y) {}
record Line(Point start, Point end) {}
public class RecordPatternDemo {
// 基础record模式
public static void printPoint(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y);
}
}
// 嵌套record模式
public static void printLine(Object obj) {
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.println("从(" + x1 + "," + y1 + ")到(" + x2 + "," + y2 + ")");
}
}
// 在switch中使用
public static String describe(Object obj) {
return switch (obj) {
case Line(Point p1, Point p2) ->
"连接两点" + p1 + "到" + p2;
case Point(int x, int y) ->
"单个点(" + x + "," + y + ")";
default -> "其他";
};
}
}
3.5 String Templates(字符串模板)- 预览特性
JDK 21引入了字符串模板,虽然还在预览阶段,但值得期待:
// 传统字符串拼接
String name = "张三";
int age = 25;
String message = "我叫" + name + ",今年" + age + "岁。";
// JDK 21字符串模板
String message = STR."我叫 \{name},今年 \{age} 岁。";
// 模板处理器
String json = JSON."""
{
"name": "\{name}",
"age": \{age}
}
""";
// 计算表达式
int a = 10, b = 20;
String calc = STR."\{a} + \{b} = \{a + b}"; // "10 + 20 = 30"
四、性能对比:JDK 8 → 17 → 21
4.1 GC性能对比
┌─────────────────────────────────────────────────────────────────────┐
│ GC性能演进 │
│ │
│ JDK 8 JDK 17/21 │
│ ────── ──────── │
│ CMS/ParNew ZGC / G1 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ GC时 │ │ GC时 │ │
│ │ 停顿 │ │ 停顿 │ │
│ │ │ │ │ │
│ │ ██████ │ ← 100-500ms │ · │ ← <1ms │
│ │ ██████ │ │ │ │
│ │ ██████ │ │ │ │
│ └─────────┘ └─────────┘ │
│ │
│ 吞吐量:~85% 吞吐量:>95% │
│ 内存占用:基准 内存占用:-10% │
│ STW停顿:数百ms STW停顿:亚毫秒 │
│ │
└─────────────────────────────────────────────────────────────────────┘
ZGC的配置(推荐在JDK 17+使用):
# 启动参数
java -XX:+UseZGC -Xmx4g -Xms4g -XX:+ZGenerational YourApp
4.2 启动时间对比
/**
* 简单测试类
*/
public class SimpleStartup {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
启动时间测试(近似值):
| JDK版本 | 启动时间 | 相对值 |
|---|---|---|
| JDK 8 | 200ms | 100% |
| JDK 11 | 150ms | 75% |
| JDK 17 | 120ms | 60% |
| JDK 21 | 100ms | 50% |
Class Data Sharing (CDS) 加速启动:
# JDK 17/21:生成AppCDS
java -XX:ArchiveClassesAtExit=app.jsa -jar your-app.jar
# 运行:使用AppCDS
java -XX:SharedArchiveFile=app.jsa -jar your-app.jar
4.3 内存占用对比
┌─────────────────────────────────────────────────────────────────────┐
│ 内存占用对比 │
│ │
│ JVM参数:-Xmx2g │
│ │
│ JDK 8(JIT warmed up): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Metaspace │ Heap │ Code Cache │ Thread Stacks │ │
│ │ 50MB │ 500MB │ 50MB │ 200MB (200线程×1MB)│ │
│ └──────────────────────────────────────────────────────┘ │
│ 总计:~800MB │
│ │
│ JDK 21(虚拟线程支持): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Metaspace │ Heap │ Code Cache │ Thread Stacks │ │
│ │ 40MB │ 500MB │ 40MB │ 2MB (200线程×10KB) │ │
│ └──────────────────────────────────────────────────────┘ │
│ 总计:~600MB(减少25%) │
│ │
│ 如果使用10000个虚拟线程: │
│ Thread Stacks ≈ 100MB(vs 10GB平台线程) │
│ │
└─────────────────────────────────────────────────────────────────────┘
五、升级指南:从JDK 8到JDK 21
5.1 升级路径规划
┌─────────────────────────────────────────────────────────────────────┐
│ 升级路径推荐 │
│ │
│ 保守升级(企业级): │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ JDK 8 │───►│ JDK 11 │───►│ JDK 17 │───► 生产验证 │
│ └────────┘ └────────┘ └────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌────────┐ │
│ └───────────────────────────────────────►│ JDK 21 │ │
│ └────────┘ │
│ 激进升级(新项目): │
│ ┌────────┐ │
│ │ JDK 21 │───► 直接使用最新LTS,拥抱未来 │
│ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5.2 JDK 8到JDK 17:必须注意的破坏性变更
/**
* 1. 移除的API
*/
public class RemovedAPIs {
// ❌ JDK 17已移除:java.xml.bind (JAXB)
// 如果项目依赖,需要添加以下Maven依赖
/*
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.0</version>
</dependency>
*/
// ❌ JDK 17已移除:javax.activation
// ❌ JDK 17已移除:javax.annotation (部分)
// ❌ JDK 17已移除:javax.corba
// ❌ JDK 17已移除:javax.transaction
}
/**
* 2. 字符串操作变更
*/
public class StringChanges {
public static void main(String[] args) {
// JDK 8:字符串字面量支持行终止符
String old = "line1\nline2";
// JDK 17:strip() vs trim()
String s = " hello ";
System.out.println(s.strip()); // "hello"(推荐,Unicode感知)
System.out.println(s.trim()); // "hello"
System.out.println(s.stripLeading()); // "hello "
System.out.println(s.stripTrailing()); // " hello"
// JDK 11+:isBlank(), lines(), repeat()
System.out.println(" ".isBlank()); // true(JDK 11+)
System.out.println("hello\nworld".lines()); // 流式处理行
// JDK 17:Text Blocks(见前述章节)
}
}
/**
* 3. 集合API增强
*/
public class CollectionChanges {
public static void main(String[] args) {
// JDK 9+:List, Set, Map工厂方法
List<String> list = List.of("a", "b", "c"); // 不可变
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("a", 1, "b", 2);
// JDK 16+:toList()替代collect(Collectors.toList())
List<String> result = list.stream()
.filter(s -> s.length() > 1)
.toList(); // JDK 16+,更简洁
// JDK 10+:var类型推断
var filtered = list.stream()
.filter(s -> s.length() > 1)
.toList();
}
}
/**
* 4. Optional/Stream增强
*/
public class OptionalStreamChanges {
public static void main(String[] args) {
// JDK 9+:Optional新增方法
Optional<String> opt = Optional.ofNullable("hello");
opt.ifPresentOrElse(
s -> System.out.println(s),
() -> System.out.println("为空")
);
// JDK 9+:or()
String result = opt.or(() -> Optional.of("default"))
.map(s -> s.toUpperCase())
.orElse("DEFAULT");
// JDK 16+:Stream.toList()返回不可变List
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.toList(); // 不可变List
}
}
/**
* 5. HTTP Client(JDK 11+)
*/
public class HttpClientChanges {
// JDK 8:使用HttpURLConnection或Apache HttpClient
// JDK 11+:内置HTTP Client(支持HTTP/2, 异步)
public static void main(String[] args) throws Exception {
// JDK 11+ HTTP Client
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.GET()
.build();
// 同步调用
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 异步调用
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
}
}
5.3 升级检查清单
/**
* 升级前检查清单
*/
public class UpgradeChecklist {
/**
* ✅ 1. 依赖版本检查
*/
public void checkDependencies() {
// 检查Spring版本
// Spring 5.3.x+ 支持JDK 17
// Spring 6.x 要求JDK 17+
// 检查数据库驱动
// MySQL Connector/J 8.0+ 支持JDK 17
// PostgreSQL JDBC 42.3+ 支持JDK 17
// 检查Jackson版本
// Jackson 2.13+ 支持JDK 17
}
/**
* ✅ 2. 反射和内部API
*/
public void checkReflection() {
// JDK 17开始,强封装内部API
// 使用--add-opens允许访问
// 错误:Illegal access: xxx
// 解决:添加JVM参数
/*
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
*/
}
/**
* ✅ 3. 第三方库兼容性
*/
public void checkThirdPartyLibraries() {
// Lombok 1.18.24+ 支持JDK 17
// Spring Boot 2.7.x 支持JDK 17(Spring Boot 3.x要求JDK 17+)
// Hibernate 6.0+ 要求JDK 17
}
}
5.4 Maven/Gradle配置
Maven配置:
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- JAXB替代 -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
<!-- Maven编译器插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
Gradle配置:
plugins {
id 'java'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType(JavaCompile) {
options.compilerArgs.addAll([
'--release', '17'
])
}
六、企业选型建议
6.1 版本选择决策树
┌─────────────────────────────────────────────────────────────────────┐
│ JDK版本选择决策树 │
│ │
│ ┌───────────┐ │
│ │ 新项目? │ │
│ └─────┬─────┘ │
│ │ │
│ ┌────────────────┴────────────────┐ │
│ │ Yes │ No │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ JDK 21 LTS │ │ 现在用哪个版本? │ │
│ │ 最新LTS │ └────────┬────────┘ │
│ │ 直接起飞 │ │ │
│ └─────────────────┘ ┌─────────────┼─────────────┐ │
│ │ JDK 8 │ JDK 11+ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 紧急升级到 │ │ 升级到JDK 17 │ │
│ │ JDK 17 │ │ 或 JDK 21 │ │
│ │ (需过渡JDK11) │ │ (视情况而定) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
6.2 不同场景推荐
| 场景 | 推荐版本 | 理由 |
|---|---|---|
| 新项目 | JDK 21 | 使用最新LTS,直接起飞 |
| Spring Boot 3.x | JDK 17+ | Spring 6要求JDK 17+ |
| Spring Boot 2.x | JDK 17 | 可升级,但需评估 |
| 遗留系统 | JDK 17 | 分阶段升级,先稳定 |
| 高并发网关 | JDK 21 | 虚拟线程大幅提升性能 |
| 数据分析 | JDK 17 | ZGC满足需求,虚拟线程非必需 |
6.3 升级收益量化
┌─────────────────────────────────────────────────────────────────────┐
│ 升级收益一览 │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ JDK 8 → JDK 17 │ │
│ │ ────────────────── │ │
│ │ ✅ GC停顿:100ms → <10ms(ZGC) │ │
│ │ ✅ 启动速度:提升40% │ │
│ │ ✅ 新语法:sealed class, record, pattern matching │ │
│ │ ✅ 代码量:减少30-50%(新语法特性) │ │
│ │ ⚠️ 迁移成本:中等(需检查依赖兼容性) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ JDK 17 → JDK 21 │ │
│ │ ────────────────── │ │
│ │ ✅ 虚拟线程:并发性能提升10-100倍(IO密集型) │ │
│ │ ✅ GC停顿:<1ms(分代ZGC) │ │
│ │ ✅ 新语法:switch模式匹配增强,record增强 │ │
│ │ ⚠️ 迁移成本:低(21是17的增量升级) │ │
│ │ 💡 建议:IO密集型优先升级 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
七、面试重点总结
7.1 必问知识点
Q1: JDK 17有哪些重要的新特性?
1. 密封类(Sealed Classes)- JEP 409
- 控制类的继承层次
- permits关键字指定允许的子类
2. Pattern Matching for instanceof - JEP 394
- 类型检查和转换一步完成
- if (obj instanceof String s) { s.xxx }
3. Switch表达式增强 - JEP 420
- 支持箭头函数
- 支持yield关键字
4. Record类 - JEP 395
- 不可变数据类
- 自动生成构造方法、getter、equals、hashCode、toString
5. 文本块 - JEP 378
- 三引号字符串
- 支持格式化
Q2: 虚拟线程的原理是什么?
核心:M:N调度模型
1. M个虚拟线程 → N个载体线程(平台线程)
2. 虚拟线程由JVM管理,栈大小约1KB(vs 平台线程1MB)
3. 遇到阻塞操作(IO、sleep)时,yield让出载体线程
4. 载体线程可被其他虚拟线程复用
5. 效果:大量并发时,内存占用降低99%
Q3: 虚拟线程和平台线程有什么区别?
| 特性 | 平台线程 | 虚拟线程 |
|--------------|-------------------|----------------------|
| 创建成本 | 高(~1MB栈) | 低(~1KB栈) |
| 调度 | OS内核 | JVM |
| 阻塞处理 | 占用载体线程 | yield释放载体线程 |
| 适用场景 | CPU密集型 | IO密集型 |
| 使用方式 | Thread.new | Thread.ofVirtual() |
Q4: 为什么JDK 17要替代CMS GC?
1. CMS是串行的,停顿时间长
2. G1是并行+增量式的,停顿可控
3. ZGC是并发收集器,停顿<1ms
4. JDK 17推荐使用ZGC或G1
Q5: 升级JDK需要考虑哪些兼容性问题?
1. 移除的API:JAXB, JAF, javax.annotation部分
2. 强封装内部API:需要--add-opens参数
3. 第三方库版本:需要升级到支持新JDK的版本
4. 反射使用:检查是否使用了内部API
7.2 知识图谱
┌──────────────────────────────────────────────────────────────────────────┐
│ Java版本演进知识图谱 │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ JDK 8 → 11 过渡 │ │
│ │ ├─ 模块系统(Jigsaw) │ │
│ │ ├─ HTTP Client │ │
│ │ ├─ var类型推断 │ │
│ │ └─ 集合工厂方法 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ JDK 11 → 17 进阶 │ │
│ │ ├─ Record类(不可变数据) │ │
│ │ ├─ Sealed类(继承控制) │ │
│ │ ├─ Pattern Matching(instanceof/switch) │ │
│ │ ├─ Text Blocks(文本块) │ │
│ │ └─ ZGC/G1优化 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ JDK 17 → 21 飞跃 │ │
│ │ ├─ Virtual Threads(虚拟线程) ← 革命性更新 │ │
│ │ ├─ Switch模式匹配完整版 │ │
│ │ ├─ Record模式增强 │ │
│ │ └─ 分代ZGC │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
结语
这篇文章我们从Java版本演进讲起,深入分析了JDK 17和JDK 21的核心特性,对比了传统线程和虚拟线程的原理,并给出了详细的升级指南。
核心要点回顾:
- JDK 17是自JDK 8以来最重要的LTS版本,引入了密封类、Record、模式匹配等革命性语法
- JDK 21的虚拟线程是并发编程的革命,让IO密集型任务性能提升10-100倍
- 升级建议:新项目直接用JDK 21,遗留系统逐步升级到JDK 17
- 升级注意事项:检查第三方库兼容性,注意移除的API,强封装内部API
- 性能提升:GC停顿从数百毫秒降到亚毫秒,启动时间减少40%
希望这篇文章能帮助大家理解Java版本的演进,在实际开发中做出更好的技术选择!
本文首发于掘金,同步更新于CSDN
更多优质内容,欢迎关注我的博客