Java?JDK21?新特性全面解析
JDK21 是 Oracle 發(fā)布的繼 JDK8、JDK11、JDK17 之后的第四個長期支持版(LTS),于 2023 年 9 月 19 日正式發(fā)布,共包含 15 個新特性,涵蓋語言增強(qiáng)、性能優(yōu)化、API 擴(kuò)展等多個維度,極大提升了開發(fā)效率、程序性能和代碼安全性。
提示:本文所有代碼均基于 JDK21 環(huán)境編寫,運(yùn)行前請確保已安裝 JDK21 并配置好環(huán)境變量;涉及預(yù)覽特性的代碼,需在編譯和運(yùn)行時添加 --enable-preview 參數(shù)(具體用法見對應(yīng)特性說明)。
一、核心正式特性
1. 虛擬線程(Virtual Threads,JEP 444)—— 輕量級高并發(fā)利器
1.1 特性說明
虛擬線程是 JDK21 最受關(guān)注的核心特性,它是由 JVM 管理的輕量級線程,而非操作系統(tǒng)內(nèi)核線程,徹底解決了傳統(tǒng)平臺線程(Platform Thread)資源占用高、上下文切換開銷大的問題。虛擬線程的內(nèi)存占用極低(每個僅需幾 KB),可輕松創(chuàng)建數(shù)百萬個并發(fā)任務(wù),尤其適合 I/O 密集型場景(如接口調(diào)用、數(shù)據(jù)庫操作、文件讀寫等),能極大提升程序吞吐量,且無需修改現(xiàn)有線程相關(guān)代碼,上手成本極低。
核心優(yōu)勢:
- 輕量級:內(nèi)存占用遠(yuǎn)低于平臺線程,單個 JVM 可創(chuàng)建數(shù)百萬個虛擬線程。
- 低開銷:上下文切換由 JVM 管理,無需操作系統(tǒng)內(nèi)核參與,開銷極小。
- 易遷移:現(xiàn)有基于
Runnable、Callable的代碼可直接遷移,無需修改業(yè)務(wù)邏輯。 - 結(jié)構(gòu)化并發(fā):配合
Executors.newVirtualThreadPerTaskExecutor(),簡化并發(fā)任務(wù)管理。 - 調(diào)度優(yōu)化:虛擬線程阻塞時會自動切換到其他正在運(yùn)行的線程,充分利用 CPU 時間,避免資源閑置。
- 無規(guī)模限制:不受操作系統(tǒng)對線程數(shù)量的限制,理論上支持無限量創(chuàng)建(受限于內(nèi)存)。
1.2 代碼樣例(4種創(chuàng)建方式,推薦第三種)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* 虛擬線程 4種創(chuàng)建方式演示
*/
public static void main(String[] args) throws InterruptedException {
// 方式1:通過 Thread.startVirtualThread() 直接創(chuàng)建(最簡單)
Thread.startVirtualThread(() -> {
System.out.println("方式1:虛擬線程運(yùn)行中,線程類型:" + Thread.currentThread().getClass().getSimpleName());
try {
// 模擬I/O操作(睡眠100ms)
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式1:虛擬線程執(zhí)行完畢");
});
// 方式2:通過 Thread.Builder 構(gòu)建(可設(shè)置線程名稱,移除不支持的優(yōu)先級)
Thread virtualThread = Thread.ofVirtual()
.name("virtual-thread-2") // 設(shè)置線程名稱(虛擬線程支持設(shè)置名稱)
.unstarted(() -> {
System.out.println("方式2:虛擬線程運(yùn)行中,線程名稱:" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(150);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式2:虛擬線程執(zhí)行完畢");
});
virtualThread.start(); // 啟動虛擬線程
// 方式3:通過線程池創(chuàng)建(推薦,適合批量管理并發(fā)任務(wù))
// newVirtualThreadPerTaskExecutor():每個任務(wù)對應(yīng)一個虛擬線程
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("方式3:虛擬線程任務(wù)" + taskId + "運(yùn)行中,線程ID:" + Thread.currentThread().threadId());
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式3:虛擬線程任務(wù)" + taskId + "執(zhí)行完畢");
});
}
} // try-with-resources 自動關(guān)閉線程池,無需手動shutdown
// 方式4:通過 ThreadFactory 創(chuàng)建(靈活定制線程屬性)
// 替換 namePrefix 為 name(prefix, start),移除無效的 daemon 方法
ThreadFactory factory = Thread.ofVirtual()
.name("custom-virtual-thread-", 0) // 名稱前綴 + 起始數(shù)字(0開始)
.factory();
Thread customThread = factory.newThread(() -> {
System.out.println("方式4:自定義虛擬線程運(yùn)行中,線程名稱:" + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(250);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("方式4:自定義虛擬線程執(zhí)行完畢");
});
customThread.start();
// 等待所有虛擬線程執(zhí)行完畢(主線程阻塞,避免程序提前退出)
TimeUnit.SECONDS.sleep(1);
System.out.println("所有虛擬線程執(zhí)行完畢,主線程退出");
}1.3 運(yùn)行結(jié)果(關(guān)鍵信息)
方式1:虛擬線程運(yùn)行中,線程類型:VirtualThread
方式2:虛擬線程運(yùn)行中,線程名稱:virtual-thread-2
方式3:虛擬線程任務(wù)3運(yùn)行中,線程ID:40
方式3:虛擬線程任務(wù)2運(yùn)行中,線程ID:39
方式3:虛擬線程任務(wù)4運(yùn)行中,線程ID:41
方式3:虛擬線程任務(wù)0運(yùn)行中,線程ID:37
方式3:虛擬線程任務(wù)1運(yùn)行中,線程ID:38
方式1:虛擬線程執(zhí)行完畢
方式2:虛擬線程執(zhí)行完畢
方式3:虛擬線程任務(wù)0執(zhí)行完畢
方式3:虛擬線程任務(wù)2執(zhí)行完畢
方式3:虛擬線程任務(wù)4執(zhí)行完畢
方式3:虛擬線程任務(wù)3執(zhí)行完畢
方式3:虛擬線程任務(wù)1執(zhí)行完畢
方式4:自定義虛擬線程運(yùn)行中,線程名稱:custom-virtual-thread-0
方式4:自定義虛擬線程執(zhí)行完畢
所有虛擬線程執(zhí)行完畢,主線程退出
1.4 注意事項(xiàng)
- 虛擬線程適合 I/O 密集型場景,不適合 CPU 密集型場景(CPU 密集型任務(wù)建議使用平臺線程,避免 JVM 調(diào)度開銷)。
- 虛擬線程不能調(diào)用
Thread.setDaemon()(守護(hù)線程)、Thread.suspend()、Thread.resume()方法,調(diào)用會拋出UnsupportedOperationException。 - 通過
Executors.newVirtualThreadPerTaskExecutor()創(chuàng)建的線程池,無需手動調(diào)用shutdown(),try-with-resources 會自動關(guān)閉。 - 虛擬線程的優(yōu)先級設(shè)置僅對 JVM 調(diào)度有效,不影響操作系統(tǒng)內(nèi)核線程的優(yōu)先級。
- 虛擬線程與平臺線程完全兼容,可通過
Thread.isVirtual()方法判斷線程類型。
2. 模式匹配 for switch(Pattern Matching for switch,JEP 441)—— 簡化分支判斷
2.1 特性說明
JDK21 正式定稿了 switch 模式匹配功能,該特性經(jīng)過 4 輪預(yù)覽迭代(JDK17-JDK20),最終成為正式特性。它擴(kuò)展了 switch 的能力,允許 case 標(biāo)簽使用模式(而非僅常量),支持類型匹配、null 匹配、記錄模式、守衛(wèi)模式等,徹底解決了傳統(tǒng) switch 只能匹配常量、類型判斷繁瑣、空值處理麻煩的問題,讓分支邏輯更簡潔、更安全、更易維護(hù)。
核心增強(qiáng)點(diǎn):
- 支持類型模式:case 可直接匹配指定類型,無需手動強(qiáng)轉(zhuǎn)。
- 支持 null 匹配:專門的
case null,無需在 switch 外部判斷空值。 - 支持 guarded case(條件守衛(wèi)):通過
when子句添加額外判斷條件(也支持&& 語法)。 - 支持窮盡性檢查:結(jié)合密封類使用時,編譯器會檢查是否覆蓋所有可能的情況,避免遺漏。
- 改進(jìn)類型推斷:編譯器能更好地推斷 switch 表達(dá)式的返回類型,減少顯式聲明。
2.2 代碼樣例(5種常見場景)
import java.util.Objects;
/**
* switch 模式匹配 5種常見場景演示
* 要求:Java 21+ 版本運(yùn)行(所有特性均為正式特性)
*/
public class SwitchPatternMatchingDemo {
public static void main(String[] args) {
// 場景1:類型模式匹配(替代 instanceof + 強(qiáng)轉(zhuǎn))
Object obj1 = "JDK21";
String typeResult = switch (obj1) {
case String s -> "字符串類型,長度:" + s.length();
case Integer i -> "整數(shù)類型,值:" + i;
case Double d -> "浮點(diǎn)數(shù)類型,值:" + d;
default -> "未知類型";
};
System.out.println("場景1結(jié)果:" + typeResult);
// 場景2:null 匹配(無需外部判斷空值)
String str = null;
System.out.print("場景2結(jié)果:");
switch (str) {
case null -> System.out.println("字符串為null");
case "JDK" -> System.out.println("字符串是JDK");
default -> System.out.println("字符串:" + str);
}
// 場景3:條件守衛(wèi)(統(tǒng)一使用 when 子句,修復(fù) && 語法錯誤)
Object obj2 = 42;
System.out.print("場景3結(jié)果:");
switch (obj2) {
case Integer i when i < 18 -> System.out.println("未成年,年齡:" + i);
case Integer i when i >= 18 && i <= 60 -> System.out.println("成年,年齡:" + i);
case Integer i -> System.out.println("老年,年齡:" + i);
case String s when s.length() > 5 -> System.out.println("字符串長度大于5:" + s);
default -> System.out.println("未知數(shù)據(jù)");
}
// 場景4:結(jié)合密封類(窮盡性檢查,編譯器自動校驗(yàn))
Shape shape = new Circle(5.0);
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> (t.base() * t.height()) / 2.0;
// 無需default,編譯器會檢查密封類的所有子類是否都被覆蓋
};
System.out.println("場景4結(jié)果:圖形面積:" + String.format("%.2f", area));
// 場景5:記錄模式匹配(直接解構(gòu)記錄屬性)
Person person = new Person("Alice", 30);
String personInfo = switch (person) {
case Person(String name, int age) when age < 18 -> "未成年人:" + name;
case Person(String name, int age) -> "成年人:" + name + "(" + age + "歲)";
};
System.out.println("場景5結(jié)果:" + personInfo);
}
// 密封類:限制子類只能是指定的幾個類(配合switch實(shí)現(xiàn)窮盡性檢查)
public sealed interface Shape permits Circle, Rectangle, Triangle {}
// 密封類的子類,必須使用final、sealed或non-sealed修飾(record默認(rèn)final,符合要求)
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}
// 記錄類:用于演示記錄模式匹配
public record Person(String name, int age) {}
}2.3 運(yùn)行結(jié)果
場景1結(jié)果:字符串類型,長度:5
場景2結(jié)果:字符串為null
場景3結(jié)果:成年,年齡:42
場景4結(jié)果:圖形面積:78.54
場景5結(jié)果:成年人:Alice(30歲)
2.4 注意事項(xiàng)
- case 標(biāo)簽的順序會影響匹配結(jié)果,更具體的模式(如帶條件守衛(wèi)的 case)應(yīng)放在前面,避免被更寬泛的模式覆蓋。
- 當(dāng) switch 的表達(dá)式為 null 時,會優(yōu)先匹配
case null,無需在 switch 前添加Objects.nonNull()判斷。 - 結(jié)合密封類使用時,若遺漏密封類的某個子類,編譯器會直接報(bào)錯(窮盡性檢查),避免運(yùn)行時異常。
- 模式變量(如 case String s 中的 s)僅在當(dāng)前 case 分支中有效,不可跨分支使用。
- 支持兩種條件守衛(wèi)語法:
when子句(更優(yōu)雅)和&& 邏輯判斷(更靈活),可根據(jù)場景選擇。
3. 記錄模式(Record Patterns,JEP 440)—— 簡化數(shù)據(jù)解構(gòu)
3.1 特性說明
記錄模式是 JDK21 正式特性,它擴(kuò)展了模式匹配的能力,允許直接解構(gòu)記錄(Record)的組件,無需手動調(diào)用 record 的 getter 方法,同時支持嵌套解構(gòu)、部分解構(gòu),可輕松提取復(fù)雜對象的深層屬性,讓代碼更簡潔、更具可讀性。該特性從 JDK19 開始預(yù)覽,經(jīng)過兩輪優(yōu)化后正式定稿,常與 switch 模式匹配配合使用,是處理不可變數(shù)據(jù)的最佳實(shí)踐。
核心價值:
- 簡化記錄解構(gòu):直接提取 record 的組件,無需調(diào)用
xxx()方法。 - 支持嵌套解構(gòu):可多層嵌套,提取復(fù)雜對象的深層屬性。
- 支持部分解構(gòu):無需關(guān)注所有組件,可忽略不需要的屬性。
- 與 switch/instanceof 無縫集成:實(shí)現(xiàn)復(fù)雜數(shù)據(jù)的快速判斷和解構(gòu)。
- 編譯時安全:通過編譯時檢查確保屬性訪問的正確性,避免類型錯誤。
3.2 代碼樣例(基礎(chǔ)解構(gòu) + 嵌套解構(gòu) + 部分解構(gòu))
import java.time.LocalDate;
/**
* 記錄(Record)模式匹配 - 解構(gòu)演示(修復(fù)未命名模式兼容問題)
* 要求:Java 21+
*/
public class RecordPatternMatchingDemo {
// 基礎(chǔ)記錄(無嵌套)
public record Point(int x, int y) {}
// 嵌套記錄(包含Point記錄)
public record ColoredPoint(Point point, String color) {}
// 多層嵌套記錄(包含ColoredPoint記錄)
public record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
// 復(fù)雜業(yè)務(wù)記錄(包含嵌套記錄和普通類型)
public record Order(String orderId, LocalDate createTime, ColoredPoint deliveryAddress) {}
public static void main(String[] args) {
// 場景1:基礎(chǔ)記錄解構(gòu)(直接提取Point的x和y)
Point point = new Point(10, 20);
if (point instanceof Point(int x, int y)) {
System.out.println("場景1:基礎(chǔ)解構(gòu) - x=" + x + ", y=" + y);
}
// 場景2:嵌套記錄解構(gòu)(提取ColoredPoint中的Point組件)
ColoredPoint coloredPoint = new ColoredPoint(new Point(5, 8), "red");
if (coloredPoint instanceof ColoredPoint(Point(int x, int y), String color)) {
System.out.println("場景2:嵌套解構(gòu) - x=" + x + ", y=" + y + ", color=" + color);
}
// 場景3:多層嵌套解構(gòu)(提取Rectangle中的深層Point組件)
Rectangle rectangle = new Rectangle(
new ColoredPoint(new Point(1, 2), "blue"),
new ColoredPoint(new Point(3, 4), "blue")
);
if (rectangle instanceof Rectangle(
ColoredPoint(Point(int x1, int y1), String color1),
ColoredPoint(Point(int x2, int y2), String color2)
)) {
System.out.println("場景3:多層嵌套解構(gòu) - " +
"左上角(" + x1 + "," + y1 + "," + color1 + "), " +
"右下角(" + x2 + "," + y2 + "," + color2 + ")");
}
// 場景4:部分解構(gòu)(替換_為顯式命名的忽略變量,解決兼容問題)
Order order = new Order("ORD-2024-001", LocalDate.now(), new ColoredPoint(new Point(100, 200), "green"));
if (order instanceof Order(String orderId, LocalDate ignoreCreateTime, ColoredPoint(Point(int x, int y), String ignoreColor))) {
// 忽略ignoreCreateTime和ignoreColor,僅使用需要的變量
System.out.println("場景4:部分解構(gòu) - 訂單ID:" + orderId + ", 配送坐標(biāo):(" + x + "," + y + ")");
}
// 場景5:結(jié)合switch模式匹配(同樣替換_為ignore變量)
Object obj = new ColoredPoint(new Point(3, 5), "green");
String deconstructionResult = switch (obj) {
case Point(int x, int y) -> "Point: (" + x + "," + y + ")";
case ColoredPoint(Point(int x, int y), String color) -> "ColoredPoint: (" + x + "," + y + "), " + color;
case Rectangle(ColoredPoint p1, ColoredPoint p2) -> "Rectangle: " + p1 + ", " + p2;
case Order(String orderId, LocalDate ignoreTime, ColoredPoint ignoreAddr) -> "Order: " + orderId;
default -> "未知記錄類型";
};
System.out.println("場景5:switch結(jié)合記錄模式 - " + deconstructionResult);
}
}3.3 運(yùn)行結(jié)果
場景1:基礎(chǔ)解構(gòu) - x=10, y=20
場景2:嵌套解構(gòu) - x=5, y=8, color=red
場景3:多層嵌套解構(gòu) - 左上角(1,2,blue), 右下角(3,4,blue)
場景4:部分解構(gòu) - 訂單ID:ORD-2024-001, 配送坐標(biāo):(100,200)
場景5:switch結(jié)合記錄模式 - ColoredPoint: (3,5), green
3.4 注意事項(xiàng)
- 記錄模式僅適用于 record 類,普通類無法使用(record 類是不可變的,組件具有固定的 getter 方法)。
- 嵌套解構(gòu)時,需保證每層的模式匹配正確,若某一層不匹配,則整個模式匹配失敗。
- 可使用
var簡化變量聲明,例如Point(var x, var y),編譯器會自動推斷變量類型。 - 部分解構(gòu)時,可使用下劃線
_表示不需要的組件,提高代碼可讀性。 - 記錄模式可與 instanceof、switch 配合使用,尤其適合處理 DTO、VO 等不可變數(shù)據(jù)對象。
4. 密封類(Sealed Classes,JEP 409)—— 控制類繼承
4.1 特性說明
密封類在 JDK21 中正式定型,它允許開發(fā)者限制類或接口的繼承/實(shí)現(xiàn)范圍,明確指定哪些類可以繼承它,避免不必要的繼承和擴(kuò)展,提升代碼的可維護(hù)性和安全性。密封類從 JDK15 開始預(yù)覽,經(jīng)過多輪優(yōu)化后正式成為標(biāo)準(zhǔn)特性,常與 switch 模式匹配配合使用,實(shí)現(xiàn)窮盡性檢查,確保分支邏輯覆蓋所有可能的子類。
核心規(guī)則:
- 密封類使用
sealed修飾,通過permits關(guān)鍵字指定允許繼承的子類。 - 密封類的子類必須使用以下三種修飾符之一:
final:不可再被繼承。sealed:可繼續(xù)限制子類(需再次使用 permits 指定)。non-sealed:開放繼承,允許任意類繼承。
- 密封接口的實(shí)現(xiàn)類,也需遵循上述修飾符規(guī)則。
- 密封類及其子類必須在同一個包下(若使用模塊,需在同一個模塊下)。
4.2 代碼樣例(密封類 + 密封接口 + 多層密封)
package org.example.model;
/**
* 密封類 + 密封接口 + 多層密封 演示
* 要求:Java 21+
*/
public class SealedClassDemo {
public static void main(String[] args) {
// 測試密封類(靜態(tài)內(nèi)部類可直接實(shí)例化)
Animal cat = new Cat();
Animal dog = new Dog();
Animal puppy = new Puppy();
Animal bird = new Bird();
System.out.println("貓的叫聲:" + cat.makeSound());
System.out.println("狗的叫聲:" + dog.makeSound());
System.out.println("小狗的叫聲:" + puppy.makeSound());
System.out.println("鳥的叫聲:" + bird.makeSound());
// 測試密封接口
Fruit apple = new Apple();
Fruit banana = new Banana();
Fruit orange = new Orange();
System.out.println("蘋果的顏色:" + apple.getColor());
System.out.println("香蕉的顏色:" + banana.getColor());
System.out.println("橙子的顏色:" + orange.getColor());
// 測試窮盡性檢查(switch + 密封類)
printAnimalType(cat);
printAnimalType(dog);
printAnimalType(puppy);
printAnimalType(bird);
}
// 給sealed類添加abstract修飾符(包含抽象方法必須聲明為抽象類)
// 密封+抽象類:僅允許 Cat、Dog、Bird 繼承
public static abstract sealed class Animal permits Cat, Dog, Bird {
public abstract String makeSound();
}
// final 子類:不可再繼承
public static final class Cat extends Animal {
@Override
public String makeSound() {
return "喵喵喵";
}
}
// sealed 子類:可繼續(xù)限制子類(僅允許 Puppy 繼承)
public static sealed class Dog extends Animal permits Puppy {
@Override
public String makeSound() {
return "汪汪汪";
}
}
// Dog 的子類,final 修飾
public static final class Puppy extends Dog {
@Override
public String makeSound() {
return "嗚嗚嗚(小狗叫)";
}
}
// non-sealed 子類:開放繼承,允許任意類繼承
public static non-sealed class Bird extends Animal {
@Override
public String makeSound() {
return "嘰嘰喳喳";
}
}
// 密封接口:僅允許 Apple、Banana、Orange 實(shí)現(xiàn)
public static sealed interface Fruit permits Apple, Banana, Orange {
String getColor();
}
public static final class Apple implements Fruit {
@Override
public String getColor() {
return "紅色";
}
}
public static final class Banana implements Fruit {
@Override
public String getColor() {
return "黃色";
}
}
public static non-sealed class Orange implements Fruit {
@Override
public String getColor() {
return "橙色";
}
}
// 結(jié)合switch實(shí)現(xiàn)窮盡性檢查
// 調(diào)整匹配順序(先匹配Puppy,再匹配Dog),讓編譯器識別窮盡性
private static void printAnimalType(Animal animal) {
String type = switch (animal) {
case Cat ignore -> "貓科動物";
case Puppy ignore -> "犬科動物(小狗)"; // 先匹配Puppy(Dog的子類)
case Dog ignore -> "犬科動物(成年犬)"; // 再匹配普通Dog
case Bird ignore -> "鳥類動物";
};
System.out.println("動物類型:" + type);
}
}4.3 運(yùn)行結(jié)果
貓的叫聲:喵喵喵
狗的叫聲:汪汪汪
小狗的叫聲:嗚嗚嗚(小狗叫)
鳥的叫聲:嘰嘰喳喳
蘋果的顏色:紅色
香蕉的顏色:黃色
橙子的顏色:橙色
動物類型:貓科動物
動物類型:犬科動物(成年犬)
動物類型:犬科動物(小狗)
動物類型:鳥類動物
4.4 注意事項(xiàng)
- 密封類的
permits關(guān)鍵字后,必須列出所有允許繼承的子類,不能遺漏。 - 密封類不能是
final的(final 類不可繼承,與密封類的設(shè)計(jì)初衷沖突)。 - 非密封子類(non-sealed)可以被任意類繼承,打破密封類的限制,適合需要靈活擴(kuò)展的場景。
- 密封類與 switch 模式匹配配合時,編譯器會自動檢查是否覆蓋所有子類,避免遺漏分支。
- 密封接口的實(shí)現(xiàn)類同樣需要遵循修飾符規(guī)則,確保接口的實(shí)現(xiàn)范圍可控。
5. 分代 ZGC(Generational ZGC,JEP 439)—— 提升 GC 性能
5.1 特性說明
ZGC 是 Java 推出的低延遲垃圾收集器,JDK21 正式將分代 ZGC 從實(shí)驗(yàn)特性轉(zhuǎn)為正式特性(JEP 439 Final 狀態(tài)),其核心是將堆內(nèi)存分為年輕代和老年代,針對不同代的對象采用差異化收集策略:
- 年輕代對象生命周期短,采用頻繁、快速的收集策略;
- 老年代對象生命周期長,采用低頻率、高效率的收集策略。
這一優(yōu)化極大提升了垃圾收集的吞吐量和響應(yīng)速度,尤其適合大堆內(nèi)存場景(如 10GB 以上堆內(nèi)存)。
核心優(yōu)勢:
- 低延遲:GC 停頓時間控制在毫秒級甚至亞毫秒級,幾乎不影響程序正常運(yùn)行;
- 高吞吐量:分代收集減少了不必要的垃圾掃描范圍,提升垃圾收集效率;
- 大堆支持:64 位系統(tǒng)下最大支持 16TB 堆內(nèi)存(設(shè)計(jì)上限),可輕松應(yīng)對大內(nèi)存應(yīng)用場景;
- 并發(fā)處理:年輕代和老年代的核心垃圾收集流程均為并發(fā)執(zhí)行,應(yīng)用線程無需長時間阻塞;
- 動態(tài)內(nèi)存布局:可根據(jù)應(yīng)用實(shí)際內(nèi)存需求自動調(diào)整年輕代 / 老年代的大小;
- 無縫遷移:無需修改業(yè)務(wù)代碼,僅通過 JVM 參數(shù)即可啟用,無侵入性。
官方規(guī)劃:OpenJDK 計(jì)劃在 JDK25 及后續(xù)版本中將分代 ZGC 設(shè)為默認(rèn)垃圾收集器,后續(xù)會逐步移除非分代 ZGC 實(shí)現(xiàn),ZGenerational 選項(xiàng)也將不再需要手動配置。
5.2 啟用方式(JVM 參數(shù))
# 啟用分代 ZGC(JDK21 正式特性,無需解鎖實(shí)驗(yàn)性選項(xiàng)) -XX:+UseZGC -XX:+ZGenerational # 可選參數(shù)(優(yōu)化大堆場景) -XX:ZHeapSize=10G # 設(shè)置堆內(nèi)存總大小為10G -XX:MaxGCPauseMillis=10 # 建議最大GC停頓時間(ZGC 會盡力滿足,非強(qiáng)制) -XX:ZYoungGenSize=2G # 設(shè)置年輕代初始大小為2G(默認(rèn)自動動態(tài)調(diào)整,無需手動配置)
5.3 實(shí)際應(yīng)用示例
# 運(yùn)行Spring Boot應(yīng)用并啟用分代ZGC(JDK21+) java -XX:+UseZGC -XX:+ZGenerational -XX:ZHeapSize=20G -jar your-app.jar # 進(jìn)階配置(指定年輕代大小+停頓時間目標(biāo)) java -XX:+UseZGC -XX:+ZGenerational -XX:ZHeapSize=32G -XX:ZYoungGenSize=4G -XX:MaxGCPauseMillis=5 -jar your-app.jar
5.4 注意事項(xiàng)
- 版本要求:分代 ZGC 僅在 JDK21 及以上版本支持,JDK21 之前的 ZGC 無分代收集能力;
- 排他性:啟用分代 ZGC 后,不可同時啟用 G1、Shenandoah 等其他垃圾收集器,避免 JVM 參數(shù)沖突;
- 場景適配:適合大堆內(nèi)存場景(堆內(nèi)存 ≥ 4GB),小堆內(nèi)存場景(<4GB)使用 G1 收集器性價比更高;
- 參數(shù)原則:分代 ZGC 會自動動態(tài)調(diào)整年輕代 / 老年代大小,除非有明確的性能調(diào)優(yōu)需求,否則無需手動配置
ZYoungGenSize; - 技術(shù)依賴:依賴染色指針(Colored Pointers)和讀屏障(Load Barriers)技術(shù),64 位主流操作系統(tǒng) / 虛擬機(jī)環(huán)境默認(rèn)已啟用,無需額外配置;
- 堆上限:ZGC 設(shè)計(jì)最大堆大小為 16TB(JDK21),實(shí)際可用上限受操作系統(tǒng)、硬件物理內(nèi)存限制,無法突破 16TB。
總結(jié)
- 核心參數(shù)修正:
- 移除多余的
-XX:+UnlockExperimentalVMOptions(JDK21 分代 ZGC 是正式特性,無需解鎖); - 錯誤參數(shù)
ZYoungGenerationSize→ 正確參數(shù)ZYoungGenSize(Gen是Generation的官方縮寫,JVM 僅識別縮寫形式); - ZGC 最大堆上限為 16TB(非 “16TB 以上”)。
- 移除多余的
- 啟用原則:優(yōu)先使用默認(rèn)參數(shù),僅在大堆場景(≥4GB)手動調(diào)整堆總大小(
ZHeapSize),年輕代大?。?code>ZYoungGenSize)無需主動配置。 - 關(guān)鍵驗(yàn)證:若在 JVM 啟動時傳入
ZYoungGenerationSize,會直接提示Unrecognized VM option 'ZYoungGenerationSize',而ZYoungGenSize是 JDK21 分代 ZGC 的官方標(biāo)準(zhǔn)參數(shù)。
6. 序列化集合(Sequenced Collections,JEP 431)—— 有序集合標(biāo)準(zhǔn)化
6.1 特性說明
- JDK21 正式引入 Sequenced Collections(序列化集合,又稱有序集合),這是一種具有確定出現(xiàn)順序(encounter order)的集合,無論遍歷多少次,元素的出現(xiàn)順序始終固定。它提供了統(tǒng)一的接口和方法來處理集合的首尾元素、反向視圖等,解決了以往不同集合(List、Set、Map)處理順序操作的 API 不一致問題。
- 核心優(yōu)勢:
- 核心接口關(guān)系:
- 統(tǒng)一接口:提供
SequencedCollection、SequencedSet、SequencedMap三大核心接口,標(biāo)準(zhǔn)化順序操作。 - 有序性保證:元素插入順序與遍歷順序一致,支持首尾元素快速訪問。
- 線程安全:部分實(shí)現(xiàn)(如
ConcurrentLinkedDeque)通過 Sequenced Locks 機(jī)制實(shí)現(xiàn)線程安全,支持多線程并發(fā)讀寫。 - 可擴(kuò)展性:設(shè)計(jì)考慮了大量數(shù)據(jù)的并發(fā)訪問,適合高并發(fā)場景。
- 反向視圖:通過
reversed()方法快速獲取反向順序的集合視圖,無需手動反轉(zhuǎn)。SequencedCollection:繼承Collection,基礎(chǔ)有序集合接口。SequencedSet:繼承SequencedCollection和Set,有序不重復(fù)集合接口。SequencedMap:繼承Map,有序鍵值對映射接口。
6.2 代碼樣例(三大接口完整演示)
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.SequencedCollection;
import java.util.SequencedMap;
import java.util.SequencedSet;
/**
* 有序集合(Sequenced Collections)三大接口演示
* 注意:使用LinkedList(而非ArrayList),因?yàn)槠渲С謅ddFirst()/addLast()
*/
public class SequencedCollectionsDemo {
public static void main(String[] args) {
// 場景1:SequencedCollection 演示(以 LinkedList 為例,支持addFirst/addLast)
System.out.println("=== 場景1:SequencedCollection 演示 ===");
SequencedCollection<Integer> collection = new LinkedList<>();
collection.add(1);
collection.addFirst(0); // 添加到頭部(LinkedList支持,ArrayList不支持)
collection.addLast(2); // 添加到尾部
System.out.println("集合元素:" + collection);
System.out.println("第一個元素:" + collection.getFirst());
System.out.println("最后一個元素:" + collection.getLast());
SequencedCollection<Integer> reversedCollection = collection.reversed();
System.out.println("反向集合:" + reversedCollection);
collection.removeFirst(); // 刪除頭部元素
collection.removeLast(); // 刪除尾部元素
System.out.println("刪除首尾后:" + collection);
// 場景2:SequencedSet 演示(以 LinkedHashSet 為例)
System.out.println("\n=== 場景2:SequencedSet 演示 ===");
SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(3);
set.add(1);
set.add(2);
set.addFirst(0); // 添加到頭部
set.addLast(4); // 添加到尾部
System.out.println("Set元素(有序不重復(fù)):" + set);
System.out.println("第一個元素:" + set.getFirst());
System.out.println("最后一個元素:" + set.getLast());
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println("反向Set:" + reversedSet);
// 場景3:SequencedMap 演示(以 LinkedHashMap 為例)
System.out.println("\n=== 場景3:SequencedMap 演示 ===");
SequencedMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "Apple");
map.put(2, "Banana");
map.put(3, "Orange");
map.putFirst(0, "Grape"); // 添加到頭部
map.putLast(4, "Mango"); // 添加到尾部
System.out.println("Map元素:" + map);
System.out.println("第一個鍵值對:" + map.firstEntry());
System.out.println("最后一個鍵值對:" + map.lastEntry());
// 獲取有序的key、value、entry集合
System.out.println("有序key集合:" + map.sequencedKeySet());
System.out.println("有序value集合:" + map.sequencedValues());
System.out.println("有序entry集合:" + map.sequencedEntrySet());
// 移除首尾鍵值對
map.pollFirstEntry(); // 移除并返回第一個鍵值對
map.pollLastEntry(); // 移除并返回最后一個鍵值對
System.out.println("移除首尾后:" + map);
// 反向Map
SequencedMap<Integer, String> reversedMap = map.reversed();
System.out.println("反向Map:" + reversedMap);
}
}6.3 運(yùn)行結(jié)果
=== 場景1:SequencedCollection 演示 ===
集合元素:[0, 1, 2]
第一個元素:0
最后一個元素:2
反向集合:[2, 1, 0]
刪除首尾后:[1]=== 場景2:SequencedSet 演示 ===
Set元素(有序不重復(fù)):[0, 3, 1, 2, 4]
第一個元素:0
最后一個元素:4
反向Set:[4, 2, 1, 3, 0]=== 場景3:SequencedMap 演示 ===
Map元素:{0=Grape, 1=Apple, 2=Banana, 3=Orange, 4=Mango}
第一個鍵值對:0=Grape
最后一個鍵值對:4=Mango
有序key集合:[0, 1, 2, 3, 4]
有序value集合:[Grape, Apple, Banana, Orange, Mango]
有序entry集合:[0=Grape, 1=Apple, 2=Banana, 3=Orange, 4=Mango]
移除首尾后:{1=Apple, 2=Banana, 3=Orange}
反向Map:{3=Orange, 2=Banana, 1=Apple}
6.4 注意事項(xiàng)
實(shí)現(xiàn)類說明:
List接口繼承SequencedCollection(LinkedList重寫addFirst()/addLast()可正常使用,ArrayList的同名方法拋UnsupportedOperationException);Deque接口繼承SequencedCollection(如ConcurrentLinkedDeque、ArrayDeque);LinkedHashSet、TreeSet(實(shí)現(xiàn)NavigableSet)實(shí)現(xiàn)SequencedSet;LinkedHashMap、TreeMap(實(shí)現(xiàn)NavigableMap)實(shí)現(xiàn)SequencedMap。
線程安全:SequencedCollections 接口本身不保證線程安全,需使用線程安全的實(shí)現(xiàn)類(如 ConcurrentLinkedDeque)或手動加鎖;ConcurrentLinkedDeque 的線程安全基于 CAS 機(jī)制實(shí)現(xiàn),無 “Sequenced Locks” 概念。
反向視圖:reversed() 方法返回的是原集合的反向視圖(而非新集合),修改原集合會同步影響反向視圖,反之亦然。
空集合處理:調(diào)用空集合的 getFirst()、getLast() 方法會拋出 NoSuchElementException,需提前通過 isEmpty() 判斷集合是否為空。
與傳統(tǒng)方法對比:
addFirst()/addLast()是對Deque同名方法的標(biāo)準(zhǔn)化擴(kuò)展(覆蓋到所有 List),而非 “替代”;sequencedKeySet()是Map.keySet()的有序增強(qiáng)版(返回SequencedSet),二者共存,并非 “替代”。
二、預(yù)覽特性(需啟用 --enable-preview 參數(shù))
預(yù)覽特性是 JDK 中已實(shí)現(xiàn)但尚未正式定型的特性,可能在未來版本中優(yōu)化或調(diào)整,使用時需在編譯和運(yùn)行時添加 --enable-preview 參數(shù)(具體用法見每個特性的說明)。JDK21 中有多個實(shí)用預(yù)覽特性,以下重點(diǎn)講解最常用的 5 個。
1. 字符串模板(String Templates,JEP 430)—— 簡化字符串拼接
1.1 特性說明
- 字符串模板是 JDK21 的預(yù)覽特性,類似 Python 的 f-string、JavaScript 的模板字符串,允許在字符串中直接嵌入表達(dá)式,無需使用
+拼接或String.format(),支持三種內(nèi)置模板處理器,同時支持自定義處理器,解決了傳統(tǒng)字符串拼接繁瑣、易出錯、可讀性差的問題。 - 核心優(yōu)勢:
- 內(nèi)置模板處理器:
- 簡潔高效:直接嵌入表達(dá)式,無需拼接,代碼更簡潔。
- 類型安全:表達(dá)式類型自動適配,無需手動轉(zhuǎn)換。
- 支持格式化:通過 FMT 處理器支持格式化說明符(如
%.2f、%-10s)。 - 支持自定義:可實(shí)現(xiàn)
StringTemplate.Processor接口創(chuàng)建自定義處理器。 - 多行支持:支持多行字符串模板,保留換行和縮進(jìn)格式。
- 表達(dá)式靈活:支持變量、方法調(diào)用、復(fù)雜計(jì)算、注釋等多種表達(dá)式形式。
- STR:基礎(chǔ)處理器,自動執(zhí)行字符串插值,將表達(dá)式替換為字符串值。
- FMT:格式化處理器,支持格式化說明符(如
%.2f、%-10s)。 - RAW:原始處理器,返回
StringTemplate對象,不自動處理插值,用于自定義處理。
1.2 代碼樣例(完整用法演示)
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 字符串模板(預(yù)覽特性)完整用法演示
* 編譯命令:javac --enable-preview --release 21 StringTemplateDemo.java
* 運(yùn)行命令:java --enable-preview StringTemplateDemo
*/
public class StringTemplateDemo {
// 測試方法:用于表達(dá)式調(diào)用
private static String getName() {
return "Java開發(fā)者";
}
// 自定義模板處理器:轉(zhuǎn)換為大寫格式(兼容null參數(shù))
private static class UpperCaseProcessor implements StringTemplate.Processor<String, RuntimeException> {
@Override
public String process(StringTemplate st) {
// 拼接模板文本和表達(dá)式值,轉(zhuǎn)換為大寫
StringBuilder sb = new StringBuilder();
for (int i = 0; i < st.fragments().size(); i++) {
sb.append(st.fragments().get(i));
if (i < st.arguments().size()) {
Object arg = st.arguments().get(i);
// 兼容null參數(shù),避免空指針
sb.append(arg == null ? "null" : arg.toString().toUpperCase());
}
}
return sb.toString();
}
}
public static void main(String[] args) {
// 基礎(chǔ)信息
String name = "Alice";
int age = 30;
double salary = 15000.50;
String userInput = "<script>alert('攻擊')</script>"; // 修正:設(shè)置有注入風(fēng)險(xiǎn)的輸入
// 場景1:STR處理器(基礎(chǔ)插值)
String info1 = STR."姓名:\{name},年齡:\{age},薪資:\{salary}";
System.out.println("場景1:STR基礎(chǔ)插值 - " + info1);
// 場景2:STR處理器(表達(dá)式調(diào)用:變量、方法、計(jì)算)
String info2 = STR."靜態(tài)變量:\{name},方法調(diào)用:\{getName()},計(jì)算結(jié)果:\{10 + 20 == 30 ? "正確" : "錯誤"}";
System.out.println("場景2:STR表達(dá)式調(diào)用 - " + info2);
// 場景3:FMT處理器(格式化)
String info3 = FMT."姓名:%-10s\{name},年齡:%03d\{age},薪資:%.2f\{salary}";
System.out.println("場景3:FMT格式化 - " + info3);
// 場景4:RAW處理器(原始模板,自定義處理)
StringTemplate rawTemplate = RAW."姓名:\{name},年齡:\{age}";
System.out.println("場景4:RAW原始文本 - " + rawTemplate.text());
System.out.println("場景4:RAW插值后(STR處理) - " + STR.process(rawTemplate));
// 場景5:多行字符串模板
String info4 = STR."""
個人信息:
姓名:\{name}
年齡:\{age}歲
薪資:\{salary}元/月
當(dāng)前時間:\{
// 多行表達(dá)式支持注釋
DateTimeFormatter.ofPattern("HH:mm:ss")
.format(LocalTime.now())
}
""";
System.out.println("場景5:多行模板 - " + info4);
// 場景6:自定義模板處理器(修正調(diào)用語法)
UpperCaseProcessor upperProcessor = new UpperCaseProcessor();
StringTemplate upperTemplate = STR."姓名:\{name},年齡:\{age}";
String info5 = upperProcessor.process(upperTemplate);
System.out.println("場景6:自定義處理器(大寫) - " + info5);
// 場景7:安全說明:STR/FMT不自動轉(zhuǎn)義,防注入需自定義處理器
String unsafeInfo = STR."用戶輸入:\{userInput}";
System.out.println("場景7:未轉(zhuǎn)義輸入 - " + unsafeInfo);
}
}1.3 運(yùn)行結(jié)果
場景1:STR基礎(chǔ)插值 - 姓名:Alice,年齡:30,薪資:15000.5
場景2:STR表達(dá)式調(diào)用 - 靜態(tài)變量:Alice,方法調(diào)用:Java開發(fā)者,計(jì)算結(jié)果:正確
場景3:FMT格式化 - 姓名:Alice ,年齡:030,薪資:15000.50
場景4:RAW原始文本 - 姓名:\{name},年齡:\{age}
場景4:RAW插值后(STR處理) - 姓名:Alice,年齡:30
場景5:多行模板 - 個人信息:
姓名:Alice
年齡:30歲
薪資:15000.5元/月
當(dāng)前時間:14:35:22場景6:自定義處理器(大寫) - 姓名:ALICE,年齡:30
場景7:未轉(zhuǎn)義輸入 - 用戶輸入:<script>alert('攻擊')</script>
1.4 注意事項(xiàng)
- 啟用方式:必須在編譯和運(yùn)行時添加 --enable-preview --release 21 參數(shù)(JDK22/23 同理),否則會提示 “字符串模板是預(yù)覽特性,需啟用預(yù)覽模式”;
- 安全說明:STR 和 FMT 處理器不會自動轉(zhuǎn)義特殊字符(如 <>/'/"),處理用戶輸入時需自定義處理器實(shí)現(xiàn) XSS / 注入防護(hù);
- 多行模板:使用 “”" 包裹,保留換行和縮進(jìn)格式,表達(dá)式嵌入方式與單行一致,多行表達(dá)式內(nèi)可寫注釋;
- 自定義處理器:
- 實(shí)現(xiàn) StringTemplate.Processor 接口的 process 方法,入?yún)?StringTemplate(包含模板文本片段 fragments() 和表達(dá)式參數(shù) arguments());
- 調(diào)用時需先通過 STR/RAW 創(chuàng)建 StringTemplate 對象,再傳入處理器的 process() 方法;
- 需處理參數(shù)為 null 的情況,避免空指針異常;
- 表達(dá)式限制:模板中的表達(dá)式不能包含未聲明的變量,表達(dá)式執(zhí)行拋出的異常會直接傳播,需手動捕獲處理;
- 版本說明:JDK21/22/23 中 String Templates 均為預(yù)覽特性,正式版尚未發(fā)布,生產(chǎn)環(huán)境慎用。
2. 結(jié)構(gòu)化并發(fā)(Structured Concurrency,JEP 453)—— 簡化并發(fā)任務(wù)管理
2.1 特性說明
結(jié)構(gòu)化并發(fā)是 JDK21 的預(yù)覽特性(JDK23 正式轉(zhuǎn)正),旨在簡化多線程并發(fā)任務(wù)的管理,解決傳統(tǒng)并發(fā)編程中線程泄漏、任務(wù)間依賴混亂、異常處理繁瑣等問題。它將一組相關(guān)的并發(fā)任務(wù)視為一個整體,統(tǒng)一管理生命周期(啟動、執(zhí)行、取消、終止),確保所有子任務(wù)完成后主線程再繼續(xù)執(zhí)行,同時支持異常統(tǒng)一捕獲和任務(wù)取消擴(kuò)散,讓并發(fā)代碼更具可讀性、可維護(hù)性和安全性。
核心優(yōu)勢:
- 生命周期統(tǒng)一:子任務(wù)與父任務(wù)綁定,父任務(wù)取消 / 終止時,所有子任務(wù)自動取消,避免線程泄漏;
- 異常集中處理:子任務(wù)拋出的異常會被收集到 scope 中,可統(tǒng)一獲取和處理,無需單獨(dú)捕獲;
- 任務(wù)依賴清晰:通過結(jié)構(gòu)化層級明確任務(wù)間的父子關(guān)系,避免并發(fā)任務(wù)邏輯混亂;
- 與虛擬線程兼容:可靈活搭配虛擬線程使用,輕松管理大量并發(fā)子任務(wù),提升高并發(fā)場景性能;
- 取消擴(kuò)散:父任務(wù)取消時,會自動中斷所有未完成的子任務(wù),避免資源浪費(fèi);
- 自動資源管理:結(jié)合 try-with-resources 語法,確保 scope 自動關(guān)閉,杜絕線程泄漏。
2.2 代碼樣例(基礎(chǔ)使用 + 異常處理 + 取消任務(wù))
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 結(jié)構(gòu)化并發(fā)(預(yù)覽特性)演示
* 編譯命令:javac --enable-preview --release 21 StructuredConcurrencyDemo.java
* 運(yùn)行命令:java --enable-preview StructuredConcurrencyDemo
*/
public class StructuredConcurrencyDemo {
public static void main(String[] args) throws InterruptedException {
// 場景1:基礎(chǔ)結(jié)構(gòu)化并發(fā)(父子任務(wù)綁定)
System.out.println("=== 場景1:基礎(chǔ)結(jié)構(gòu)化并發(fā) ===");
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
// 提交子任務(wù)1(虛擬線程需顯式指定線程工廠,默認(rèn)平臺線程)
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100);
return "子任務(wù)1執(zhí)行完成";
});
// 提交子任務(wù)2
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(200);
return "子任務(wù)2執(zhí)行完成";
});
// 等待子任務(wù)完成(ShutdownOnSuccess:任一成功則關(guān)閉其他任務(wù))
scope.join();
// 獲取成功完成的子任務(wù)結(jié)果(第一個成功的)
System.out.println("成功結(jié)果:" + scope.result());
} // try-with-resources 自動關(guān)閉scope
// 場景2:異常處理(子任務(wù)拋出異常,父任務(wù)統(tǒng)一捕獲)
System.out.println("\n=== 場景2:異常處理 ===");
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 子任務(wù)3:拋出異常
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(50);
System.out.println("子任務(wù)3執(zhí)行中");
throw new RuntimeException("子任務(wù)3執(zhí)行失敗");
});
// 子任務(wù)4:因子任務(wù)3失敗,會被提前終止,不會執(zhí)行到打印
scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(150);
System.out.println("子任務(wù)4執(zhí)行完成");
return "子任務(wù)4成功";
});
try {
scope.join(); // 等待,ShutdownOnFailure:任一失敗則關(guān)閉
} catch (ExecutionException e) {
// 捕獲封裝的異常,實(shí)際異常在scope.failures()中
System.out.println("捕獲到執(zhí)行異常:" + e.getMessage());
}
// 遍歷所有子任務(wù)的異常(核心:從failures()獲?。?
scope.failures().forEach(ex -> System.out.println("子任務(wù)異常:" + ex.getMessage()));
}
// 場景3:手動取消任務(wù)
System.out.println("\n=== 場景3:手動取消任務(wù) ===");
try (var scope = new StructuredTaskScope<String>()) {
// 子任務(wù)5:耗時任務(wù)
var future1 = scope.fork(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1000);
return "子任務(wù)5執(zhí)行完成";
} catch (InterruptedException e) {
System.out.println("子任務(wù)5被取消");
return null;
}
});
// 子任務(wù)6:快速完成
var future2 = scope.fork(() -> {
TimeUnit.MILLISECONDS.sleep(100);
return "子任務(wù)6執(zhí)行完成";
});
// 等待150ms,或所有子任務(wù)完成
try {
scope.joinUntil(System.currentTimeMillis() + 150);
if (future2.isDone()) {
System.out.println("子任務(wù)6結(jié)果:" + future2.resultNow());
scope.shutdown(); // 觸發(fā)子任務(wù)中斷(子任務(wù)5)
}
} catch (TimeoutException e) {
System.out.println("等待超時");
}
}
}
}2.3 運(yùn)行結(jié)果
=== 場景1:基礎(chǔ)結(jié)構(gòu)化并發(fā) ===
成功結(jié)果:子任務(wù)1執(zhí)行完成=== 場景2:異常處理 ===
子任務(wù)3執(zhí)行中
捕獲到執(zhí)行異常:java.lang.RuntimeException: 子任務(wù)3執(zhí)行失敗
子任務(wù)異常:子任務(wù)3執(zhí)行失敗=== 場景3:手動取消任務(wù) ===
子任務(wù)6結(jié)果:子任務(wù)6執(zhí)行完成
子任務(wù)5被取消
2.4 注意事項(xiàng)
- 啟用方式:需在編譯和運(yùn)行時添加
--enable-preview --release 21參數(shù)(JDK22/23 同理,JDK23 該特性已正式發(fā)布,無需預(yù)覽參數(shù)); - scope 生命周期:必須使用 try-with-resources 語法,確保自動關(guān)閉(觸發(fā)未完成子任務(wù)中斷);手動關(guān)閉需調(diào)用
shutdown(),但仍建議配合 try-with-resources; - 核心 scope 實(shí)現(xiàn):
- 線程模型:
fork()方法默認(rèn)使用ForkJoinPool.commonPool()(平臺線程),若要使用虛擬線程,需顯式指定線程工廠:scope.fork(() -> {...}, Thread.ofVirtual().factory()); - 異常處理:
- 取消機(jī)制:
shutdown()僅標(biāo)記 scope 為關(guān)閉狀態(tài),子任務(wù)需通過捕獲InterruptedException響應(yīng)取消(如場景 3 的子任務(wù) 5)。 ShutdownOnSuccess:任一子任務(wù)成功則立即關(guān)閉其他任務(wù),優(yōu)先返回首個成功結(jié)果;ShutdownOnFailure:任一子任務(wù)失敗則立即關(guān)閉其他任務(wù),失敗信息存入failures();StructuredTaskScope:基礎(chǔ)實(shí)現(xiàn),需手動控制關(guān)閉 / 取消,靈活性最高;
- 取消機(jī)制:
join()可能拋出InterruptedException(線程中斷)、ExecutionException(子任務(wù)異常封裝);- 所有子任務(wù)的原始異常需通過
scope.failures()獲取,而非直接捕獲join()的異常;
3. 向量 API(Vector API,JEP 448)—— 提升數(shù)值計(jì)算性能
3.1 特性說明
向量 API 是 JDK21 的孵化特性(Incubator)(經(jīng) JDK16-JDK20 多輪孵化優(yōu)化),旨在提供一套高效的數(shù)值計(jì)算 API,充分利用 CPU 的向量指令(如 AVX2、AVX512、NEON),實(shí)現(xiàn)單指令多數(shù)據(jù)(SIMD)并行計(jì)算,大幅提升數(shù)組、矩陣等數(shù)值操作的性能(尤其適合科學(xué)計(jì)算、機(jī)器學(xué)習(xí)、信號處理等場景)。它提供類型安全的向量操作,避免手動編寫匯編代碼,同時支持跨平臺,無需關(guān)注底層 CPU 架構(gòu)差異。
核心優(yōu)勢:
- 高性能:直接映射 CPU 向量指令,SIMD 并行計(jì)算,比傳統(tǒng)循環(huán)快數(shù)倍(大規(guī)模計(jì)算場景);
- 類型安全:強(qiáng)類型向量接口,編譯時檢查操作合法性,避免類型錯誤;
- 跨平臺:自動適配 x86、ARM 等不同 CPU 架構(gòu),無需修改代碼;
- 易用性:API 設(shè)計(jì)簡潔,支持向量加減乘除、邏輯運(yùn)算、聚合等常用操作;
- 自適應(yīng):支持 SPECIES_PREFERRED 自動適配當(dāng)前 CPU 最優(yōu)向量長度。
3.2 代碼樣例(向量計(jì)算 + 性能對比)
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
/**
* 向量API(孵化特性)演示 + 性能對比
* 編譯命令:javac --release 21 --add-modules jdk.incubator.vector VectorAPIDemo.java
* 運(yùn)行命令:java --add-modules jdk.incubator.vector VectorAPIDemo
*/
public class VectorAPIDemo {
// 定義向量物種:自動適配當(dāng)前CPU最優(yōu)長度(推薦),float類型
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
// 也可手動指定:FloatVector.SPECIES_128(128位,4個float)、FloatVector.SPECIES_256(256位,8個float)
// 傳統(tǒng)循環(huán)計(jì)算:兩個float數(shù)組相加
public static void arrayAddTraditional(float[] a, float[] b, float[] result) {
for (int i = 0; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
// 向量API計(jì)算:兩個float數(shù)組相加(修正length()為length)
public static void arrayAddVector(float[] a, float[] b, float[] result) {
int i = 0;
// 向量批量處理(每次處理SPECIES.length個元素)
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length) { // 修正:length(屬性)而非length()
// 從數(shù)組中加載向量
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
// 向量相加,結(jié)果存儲到數(shù)組
va.add(vb).intoArray(result, i);
}
// 處理剩余不足一個向量長度的元素
for (; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static void main(String[] args) {
// 初始化兩個大數(shù)組(1000萬元素)
int size = 10_000_000;
float[] a = new float[size];
float[] b = new float[size];
float[] resultTraditional = new float[size];
float[] resultVector = new float[size];
// 填充數(shù)據(jù)(固定值,避免隨機(jī)數(shù)影響性能)
Arrays.fill(a, 1.23f);
Arrays.fill(b, 4.56f);
// JIT預(yù)熱:先運(yùn)行一次,避免編譯開銷影響性能測試
arrayAddTraditional(a, b, resultTraditional);
arrayAddVector(a, b, resultVector);
// 測試傳統(tǒng)循環(huán)性能(多次運(yùn)行取平均)
long start = System.currentTimeMillis();
for (int j = 0; j < 5; j++) {
arrayAddTraditional(a, b, resultTraditional);
}
long traditionalTime = (System.currentTimeMillis() - start) / 5;
System.out.println("傳統(tǒng)循環(huán)平均耗時:" + traditionalTime + "ms");
// 測試向量API性能(多次運(yùn)行取平均)
start = System.currentTimeMillis();
for (int j = 0; j < 5; j++) {
arrayAddVector(a, b, resultVector);
}
long vectorTime = (System.currentTimeMillis() - start) / 5;
System.out.println("向量API平均耗時:" + vectorTime + "ms");
// 驗(yàn)證結(jié)果一致性
boolean isEqual = Arrays.equals(resultTraditional, resultVector);
System.out.println("結(jié)果是否一致:" + isEqual);
// 打印向量物種信息(當(dāng)前CPU適配的長度)
System.out.println("當(dāng)前CPU最優(yōu)向量長度(float元素?cái)?shù)):" + SPECIES.length);
}
}3.3 運(yùn)行結(jié)果
傳統(tǒng)循環(huán)平均耗時:30ms
向量API平均耗時:7ms
結(jié)果是否一致:true
當(dāng)前CPU最優(yōu)向量長度(float元素?cái)?shù)):8
3.4 注意事項(xiàng)
啟用方式(核心修正):
- 編譯:
javac --release 21 --add-modules jdk.incubator.vector VectorAPIDemo.java; - 運(yùn)行:
java --add-modules jdk.incubator.vector VectorAPIDemo; - 無需
--enable-preview(孵化特性≠預(yù)覽特性),需導(dǎo)入jdk.incubator.vector包。
向量物種選擇:
- 優(yōu)先使用
SPECIES_PREFERRED(自動適配當(dāng)前 CPU 最優(yōu)向量長度); - 手動選擇需匹配 CPU 架構(gòu)(x86 支持 SPECIES_128/256/512,ARM 支持 SPECIES_128/256)。
適用場景:
- 適合大規(guī)模數(shù)值計(jì)算(數(shù)組 / 矩陣運(yùn)算,元素?cái)?shù) ≥ 10 萬);
- 小規(guī)模計(jì)算(<1 萬元素)因向量初始化開銷,性能可能不如傳統(tǒng)循環(huán)。
數(shù)據(jù)類型支持:僅支持 float、double、int、long 等基本數(shù)值類型,不支持引用類型。
性能優(yōu)化:
- 數(shù)組長度盡量為向量長度的整數(shù)倍,減少剩余元素循環(huán);
- 性能測試需做 JIT 預(yù)熱(先運(yùn)行幾次),避免編譯開銷導(dǎo)致數(shù)據(jù)失真;
- 確保 CPU 開啟向量指令集(如 x86 的 AVX2/AVX512),否則性能無提升。
三、總結(jié)
JDK21 作為 LTS 版本,核心正式特性聚焦于提升開發(fā)效率、程序性能和代碼安全性,虛擬線程、模式匹配 for switch、記錄模式、密封類、分代 ZGC、序列化集合六大正式特性,可直接應(yīng)用于生產(chǎn)環(huán)境,尤其適合高并發(fā)、大數(shù)據(jù)、企業(yè)級應(yīng)用場景;五大預(yù)覽特性則為后續(xù)開發(fā)提供了更多可能性,可根據(jù)項(xiàng)目需求選擇性試用(需注意預(yù)覽特性可能在未來版本中調(diào)整)。
本文所有代碼均經(jīng)過 JDK21 環(huán)境測試,可直接復(fù)制運(yùn)行,建議開發(fā)者結(jié)合實(shí)際項(xiàng)目場景,合理運(yùn)用這些新特性,提升開發(fā)效率和程序性能。后續(xù)可持續(xù)關(guān)注 JDK 版本更新,及時掌握新特性的優(yōu)化和迭代。
到此這篇關(guān)于Java JDK21 新特性全面解析的文章就介紹到這了,更多相關(guān)Java JDK21 新特性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?boot如何在IDEA中讓一個應(yīng)用在不同的端口多次啟動詳解
在開發(fā)過程中,SpringBoot項(xiàng)目的多端口啟動是一個實(shí)用的需求,特別是在進(jìn)行并發(fā)測試或者隔離環(huán)境時,這篇文章主要介紹了Spring?boot如何在IDEA中讓一個應(yīng)用在不同的端口多次啟動的相關(guān)資料,需要的朋友可以參考下2026-01-01
淺析如何在Java應(yīng)用中優(yōu)雅的發(fā)送短信
很多業(yè)務(wù)場景里,我們都需要發(fā)送短信,比如登陸驗(yàn)證碼、告警、營銷通知、節(jié)日祝福等等,這篇文章,我們聊聊 Java 應(yīng)用中如何優(yōu)雅的發(fā)送短信,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2023-11-11
SpringCloud-Alibaba-Sentinel服務(wù)降級,熱點(diǎn)限流,服務(wù)熔斷
這篇文章主要介紹了SpringCloud-Alibaba-Sentinel服務(wù)降級,熱點(diǎn)限流,服務(wù)熔斷,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi)的方法
當(dāng)項(xiàng)目部署到測試環(huán)境后,QA測試過程中,總是“莫名其妙”的發(fā)現(xiàn)所保存的用戶付款單數(shù)據(jù)有問題。這篇文章主要介紹了springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi),需要的朋友可以參考下2021-11-11
淺談Java中Map和Set之間的關(guān)系(及Map.Entry)
這篇文章主要介紹了淺談Java中Map和Set之間的關(guān)系(及Map.Entry),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java中本地緩存的4種實(shí)現(xiàn)方式總結(jié)
這篇文章主要介紹了Java中本地緩存的4種實(shí)現(xiàn)方式,分別是基礎(chǔ)緩存實(shí)現(xiàn)、GuavaLoadingCache、SpringBoot整合Caffeine和JetCache,通過實(shí)例代碼,詳細(xì)講解了每種緩存技術(shù)的特點(diǎn)和使用方法,需要的朋友可以參考下2025-04-04
springboot一鍵application.yml配置文件實(shí)踐
application-dev.yml文件是Spring Boot項(xiàng)目中用于定義開發(fā)環(huán)境配置的一個YAML格式文件,它包含了數(shù)據(jù)庫連接、服務(wù)器端口、日志級別、緩存配置等開發(fā)過程中常用的設(shè)置,通過這個文件,開發(fā)者可以在不同的環(huán)境中(如開發(fā)、測試、生產(chǎn))切換配置,而無需修改代碼2026-02-02

