3月19日,Java22重磅發佈。Java22新增了12項增強功能,其中包括七個預覽特性和一個孵化器特性,這些功能都顯著到足以引起JDK增強提案(JEPs)的關註。它們涵蓋了Java語言、其API、性能以及JDK中包含的工具的改進。 ...
就在3月19日,Java22重磅發佈。Java22新增了12項增強功能,其中包括七個預覽特性和一個孵化器特性,這些功能都顯著到足以引起JDK增強提案(JEPs)的關註。它們涵蓋了Java語言、其API、性能以及JDK中包含的工具的改進。
真的捲不動了,,前段時間才將項目升級到Java17。。。。
接下來我們看看具體的新特性介紹。。。
Java語言上的改進
Unnamed Variables & Patterns - JEP 456
匿名變數和模式。當需要但未使用變數聲明或嵌套模式時,提高了可讀性。這兩者都用下劃線字元表示。
優化:
- 捕獲開發人員意圖,即給定的綁定或Lambda參數未使用,並強制執行該屬性以澄清程式並減少錯誤的機會。
比如我們可以在迴圈中這樣使用:
static int count(Iterable<Order> orders) {
int total = 0;
for (Order _ : orders) // Unnamed variable
total++;
return total;
}
或者
for (int i = 0, _ = sideEffect(); i < 10; i++) { ... i ... }
或者while
迴圈:
while (q.size() >= 3) {
var x = q.remove();
var _ = q.remove(); // Unnamed variable
var _ = q.remove(); // Unnamed variable
... new Point(x, 0) ...
}
- 通過識別必須聲明但未使用的變數(例如,在捕獲子句中)來提高所有代碼的可維護性。
String s = ...
try {
int i = Integer.parseInt(s);
... i ...
} catch (NumberFormatException _) { // Unnamed variable
System.out.println("Bad number: " + s);
}
多個catch
:
try { ... }
catch (Exception _) { ... } // Unnamed variable
catch (Throwable _) { ... } // Unnamed variable
或者這樣使用try...resource
try (var _ = ScopedContext.acquire()) { // Unnamed variable
... no use of acquired resource ...
}
在lamba中我們可以這樣使用:
...stream.collect(Collectors.toMap(String::toUpperCase,
_ -> "NODATA")) // Unnamed variable
- 允許在單個 case 標簽中出現多個模式,如果它們都沒有聲明任何模式變數。
例如:
switch (ball) {
case RedBall _ -> process(ball); // Unnamed pattern variable
case BlueBall _ -> process(ball); // Unnamed pattern variable
case GreenBall _ -> stopProcessing(); // Unnamed pattern variable
}
或者
switch (box) {
case Box(RedBall _) -> processBox(box); // Unnamed pattern variable
case Box(BlueBall _) -> processBox(box); // Unnamed pattern variable
case Box(GreenBall _) -> stopProcessing(); // Unnamed pattern variable
case Box(var _) -> pickAnotherBox(); // Unnamed pattern variable
}
通過這種改進允許我們省略名稱,未命名的模式變數使得基於類型模式的運行時數據探索在switch語句塊以及使用instanceof運算符時,視覺上更加清晰明瞭。
- 通過省略不必要的嵌套類型模式來改善記錄模式的可讀性。
Statements before super - JEP 447
構造器中的前置語句。在構造函數中,允許在顯式構造函數調用之前出現不引用正在創建的實例的語句。
優化:
- 為開發人員提供更大的自由度來表達構造函數的行為,從而使當前必須因數化為輔助靜態方法、輔助中間構造函數或構造函數參數的邏輯能夠更自然地放置。
有時我們需要驗證傳遞給超類構造函數的參數。雖然我們可以在事後進行參數驗證,但這意味著可能會進行不必要的操作。例如如下:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(value); // Potentially unnecessary work
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
}
}
Java22中的做法是聲明一個能夠快速失敗的構造函數,即在調用超類構造函數之前先驗證其參數。目前我們只能採用內聯方式實現這一點,即藉助於輔助靜態方法:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(verifyPositive(value));
}
private static long verifyPositive(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
return value;
}
}
我們還可以將驗證邏輯直接包含在構造函數內部,這段代碼將會更具可讀性。
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
super(value);
}
}
- 保留了構造函數在類實例化期間按自上而下順序運行的現有保證,確保子類構造函數中的代碼不能幹擾超類實例化。
為了給超類構造函數提供參數,我們必須執行另外的計算,再次不得不藉助於輔助方法:
public class Sub extends Super {
public Sub(Certificate certificate) {
super(prepareByteArray(certificate));
}
// 輔助方法
private static byte[] prepareByteArray(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null certificate");
return switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
...
default -> ...
};
}
}
超類構造函數接受一個位元組數組作為參數,而子類構造函數接受一個Certificate
對象作為參數。為了滿足超類構造函數調用必須為子類構造函數中的第一條語句這一限制,我們聲明瞭一個輔助方法prepareByteArray
來為此調用準備參數。
如果能夠將參數準備代碼直接嵌入到構造函數中,這段代碼會更具可讀性。在Java22中我們可以這麼做:
public Sub(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null證書");
final byte[] byteArray = switch (publicKey) {
case RSAKey rsaKey -> ... // RSA密鑰轉換為位元組數組
case DSAPublicKey dsaKey -> ... // DSA公鑰轉換為位元組數組
...
default -> ... // 其他情況處理邏輯
};
super(byteArray);
}
- 不需要對Java虛擬機進行任何更改。這種 Java 語言特性僅依賴於 JVM 當前驗證和執行構造函數中顯式構造函數調用之前出現的代碼的能力。
String Templates - JEP 459:
字元串模板。字元串模板通過將文本文字與嵌入表達式和模板處理器相結合,以產生專門的結果,來補充 Java 的現有字元串文字和文本塊。
優化:
- 通過簡化在運行時計算值的字元串的表達方式,簡化了編寫 Java 程式。
- 通過使文本和表達式混合的表達更易於閱讀,無論文本是否適合單個源行(如字元串文字)或跨越多個源行(如文本塊)。
- 通過支持模板及其嵌入表達式的驗證和轉換,改進了由用戶提供的值組成字元串並將其傳遞給其他系統(例如,構建資料庫查詢)的 Java 程式的安全性。
- **保持了靈活性,允許Java庫定義在字元串模板中使用的格式化語法。
- 簡化了接受非Java語言(例如
SQL
、XML
和JSON
)編寫的字元串的 API 的使用。 - 允許創建從文本文字和嵌入表達式計算出的非字元串值,而無需通過中間字元串表示轉換。
字元串的模板可以直接在代碼中表達,就像註釋字元串一樣,Java 運行時會自動將特定於模板的規則應用於字元串。從模板編寫字元串將使開發人員不必費力地轉義每個嵌入表達式、調用validate()
整個字元串或使用java.util.ResourceBundle
來查找本地化字元串。
比如我們可以構造一個表示JSON文檔的字元串,然後將其提供給JSON解析器:
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = """
{
"name": "%s",
"phone": "%s",
"address": "%s"
}
""".formatted(name, phone, address);
JSONObject doc = JSON.parse(json);
字元串的 JSON 結構可以直接在代碼中表達,Java運行時會JSONObject
自動將字元串轉換為。無需通過解析器進行手動繞行。
我們使用基於模板的字元串組合機制,我們就可以提高幾乎每個Jav 程式的可讀性和可靠性。這種功能將提供插值的好處,就像在其他編程語言中看到的那樣,但不太容易引入安全漏洞。它還可以減少使用將複雜輸入作為字元串的庫的繁瑣。
我們還可以使用模板STR
處理器,STR
是 Java 平臺中定義的模板處理器。它通過將模板中的每個嵌入表達式替換為該表達式的(字元串化)值來執行字元串插值。STR
是public
static
final
自動導入到每個Java源文件中的欄位。
// Embedded expressions can be strings
String firstName = "Bill";
String lastName = "Duck";
String fullName = STR."\{firstName} \{lastName}";
| "Bill Duck"
String sortName = STR."\{lastName}, \{firstName}";
| "Duck, Bill"
// Embedded expressions can perform arithmetic
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
| "10 + 20 = 30"
// Embedded expressions can invoke methods and access fields
String s = STR."You have a \{getOfferType()} waiting for you!";
| "You have a gift waiting for you!"
String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";
| "Access at 2022-03-25 15:34 from 8.8.8.8"
模板表達式的模板可以跨越多行源代碼,使用類似於文本塊的語法。
String title = "My Web Page";
String text = "Hello, world";
String html = STR."""
<html>
<head>
<title>\{title}</title>
</head>
<body>
<p>\{text}</p>
</body>
</html>
""";
| """
| <html>
| <head>
| <title>My Web Page</title>
| </head>
| <body>
| <p>Hello, world</p>
| </body>
| </html>
| """
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
| """
| {
| "name": "Joan Smith",
| "phone": "555-123-4567",
| "address": "1 Maple Drive, Anytown"
| }
| """
Implicitly Declared Classes and Instance Main Methods - JEP 463:
隱式聲明的類和實例主方法。這項Java增強引入了隱式聲明的類以及實例主方法的功能,允許開發人員在不明確顯式聲明類的情況下編寫類結構,並能夠在類實例上直接定義和執行類似於傳統main
方法的入口點。這一特性旨在簡化編程模型,特別是對於初學者和小型腳本場景,使得無需瞭解大型程式設計所需的完整類聲明結構也能快速編寫可運行的Java代碼。
優化:
總體來說可以快速學習Java。
- 提供了平穩的入門 Java 編程的途徑,因此教Java的可以逐漸介紹概念。
- 幫助初學者以簡潔的方式編寫基本程式,並隨著他們的技能增長而逐漸增加他們的代碼。
- 減少了編寫簡單程式(如腳本和命令行實用程式)的儀式感。
- 不會引入單獨的 Java 語言初學者方言。
- 不會引入單獨的初學者工具鏈;初學者的生程式應該使用編譯和運行任何Java程式的相同工具。
我們以入門Java的第一行代碼Hello World
為例:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Java22還可以隱式聲明一個類:
void main() {
System.out.println("Hello, World!");
}
還可以這樣:
String greeting() { return "Hello, World!"; }
void main() {
System.out.println(greeting());
}
或者,使用欄位,如:
String greeting = "Hello, World!";
void main() {
System.out.println(greeting);
}
Java API庫上的改進
Foreign Function & Memory API - JEP 454:
外部函數和記憶體API。允許Java程式與Java運行時之外的代碼和數據進行交互。通過高效地調用外部函數(即JVM外部的代碼)和安全地訪問外部記憶體(即JVM不管理的記憶體),該API使Java程式能夠調用本地庫並處理本地數據,而無需JNI的脆弱性和危險性。
優化
- 生產率 —— 用簡潔、可讀和純 Java 的 API 替換原生方法和 Java 本機介面(JNI)的脆弱機制。
- 性能 —— 提供與 JNI 和 sun.misc.Unsafe 相當甚至更好的外部函數和記憶體訪問開銷。
- 廣泛的平臺支持 —— 在 JVM 運行的每個平臺上啟用本地庫的發現和調用。
- 統一性 —— 提供對結構化和非結構化數據的操作方式,無限大小,多種記憶體類型(例如,本機記憶體、持久記憶體和托管堆記憶體)。
- 健全性 —— 即使在多個線程之間分配和釋放記憶體時,也保證不會出現使用後釋放的錯誤。
- 完整性 —— 允許程式執行與本地代碼和數據有關的不安全操作,但預設情況下向用戶警告此類操作。
Java22提供外部函數和記憶體API(FFM API)定義類和介面,以便開發者使用他們可以
- 控制外部記憶體
(MemorySegment
、Arena
和SegmentAllocator
)的分配和釋放, - 操作和訪問結構化的外部存儲器
MemoryLayout
和VarHandle
- 調用外部函數
(Linker
、SymbolLookup
、FunctionDescriptor
和MethodHandle
)。
FFM API在java.lang.foreign
包中。
Class-File API - JEP 457:
類文件API。提供了用於解析、生成和轉換 Java 類文件的標準 API。
優化:
- 提供用於處理類文件的API,該類文件跟蹤Java虛擬機規範定義的文件格式。class
- 使JDK組件能夠遷移到標準 API,並最終遷移到標準API刪除第三方ASM庫的JDK內部副本。
Java22為 Class-File API 採用了以下設計目標和原則。
-
類文件實體由不可變對象表示
所有類文件 實體,例如欄位、方法、屬性、位元組碼指令、註釋等, 由不可變對象表示。這有利於在以下情況下進行可靠共用 正在轉換類文件。 -
樹結構表示
類文件具有樹結構。一個類 有一些元數據(名稱、超類等)和可變數量的欄位, 方法和屬性。欄位和方法本身具有元數據,併進一步 包含屬性,包括屬性。屬性 further 包含指令、異常處理程式等。用於 導航和生成類文件應反映此結構。CodeCode -
用戶驅動的導航
我們通過類文件樹的路徑是 由用戶選擇驅動。如果用戶只關心欄位上的註釋,那麼 我們只需要解析結構內部的註釋屬性;我們不應該研究任何一個類 屬性或方法的主體,或欄位的其他屬性。用戶 應該能夠處理複合實體,例如方法,無論是作為單個實體 單位或作為其組成部分的流,根據需要。field_info -
懶惰
用戶驅動的導航可顯著提高效率,例如 不解析超過滿足用戶要求的類文件 需要。如果用戶不打算深入研究方法的內容,那麼我們 不需要解析比需要更多的結構 下一個類文件元素開始的位置。我們可以懶洋洋地膨脹和緩存, 用戶請求時的完整表示形式。method_info -
統一的流式處理和具體化視圖
與 ASM 一樣,我們希望同時支持兩者 類文件的流式處理和實例化視圖。流視圖是 適用於大多數用例,而物化視圖更 一般,因為它支持隨機訪問。我們可以提供一個物化的觀點 通過懶惰比 ASM 便宜,這是由不變性實現的。我們可以, 此外,對齊流式視圖和實例化視圖,以便它們使用通用的 辭彙表,可以協調使用,因為每個用例都很方便。 -
緊急轉換
如果類文件解析和生成 API 是 充分對齊,那麼轉換可以是一個緊急屬性,可以 不需要自己的特殊模式或重要的新 API 圖面。(ASM實現 這是通過為讀者和作者使用通用的訪客結構來實現的。如果類, 欄位、方法和代碼體是可讀和可寫的,作為 元素,則可以將轉換視為對此的平面映射操作 流,由 lambda 定義。 -
細節隱藏
類文件的許多部分(常量池、引導方法 表、堆棧圖等)派生自類文件的其他部分。它 要求用戶直接構建這些是沒有意義的;這是額外的工作 對於用戶來說,並增加了出錯的機會。API 將自動 生成與其他實體緊密耦合的實體 添加到類文件中的欄位、方法和指令。
Class-File API
在 java.lang.classfile
包和子包中。 它定義了三個主要抽象:
-
元素是對類文件某部分的一種不可變描述,可能是一個指令、屬性、欄位、方法,甚至是整個類文件。有些元素,如方法,是複合元素;除了本身是元素外,它們還包含自身的元素,可以作為一個整體處理,也可以進一步分解。
-
每種類型的複合元素都有一個對應的構建器,該構建器擁有特定的構建方法(例如,ClassBuilder::withMethod),並且也是相應元素類型的消費者。
-
最後,變換代表了一個函數,它接收一個元素和一個構建器,並調解如何(如果有的話)將該元素轉換為其他元素。
Stream Gatherers - JEP 461:
流收集器。增強了 Stream API,以支持自定義中間操作。這將允許流管道以不易通過現有內置中間操作實現的方式轉換數據。
優化:
- 使流管道更加靈活和富有表現力。
- 儘可能允許自定義中間操作來操作無限大小的流。
流(Stream)::gather(Gatherer) 是一種新的中間流操作,通過應用用戶自定義實體——收集器(Gatherer)來處理流中的元素。利用gather操作,我們可以構建高效且適用於並行處理的流,實現幾乎所有的中間操作。Stream::gather(Gatherer) 在中間操作中的作用類似於Stream::collect(Collector)在終止操作中的作用。
Gatherer用於表示對流中元素的轉換,它是java.util.stream.Gatherer介面的一個實例。Gatherer可以以一對一、一對多、多對一或多對多的方式轉換元素。它可以跟蹤已處理過的元素以影響後續元素的轉換,支持短路操作以將無限流轉換為有限流,並能啟用並行執行。例如,一個Gatherer可以從輸入流中按條件轉換一個輸入元素為一個輸出元素,直到某一條件變為真,此時開始將一個輸入元素轉換為兩個輸出元素。
Gatherer由四個協同工作的函數定義:
-
可選初始化函數提供了在處理流元素過程中維持私有狀態的對象。例如,Gatherer可以存儲當前元素,以便下次應用時比較新元素和前一個元素,並僅輸出兩者中較大的那個。實際上,這種Gatherer將兩個輸入元素轉換為一個輸出元素。
-
整合函數整合來自輸入流的新元素,可能檢查私有狀態對象,並可能向輸出流發出元素。它還可以在到達輸入流末尾之前提前終止處理;例如,一個搜索整數流中最大值的Gatherer在檢測到Integer.MAX_VALUE時可以終止處理。
-
可選組合函數可用於在輸入流標記為並行時並行評估Gatherer。若Gatherer不支持並行,則仍可作為並行流管道的一部分,但在評估時將以順序方式進行。這對於某些本質上有序因而無法並行化的操作場景非常有用。
-
可選完成函數在沒有更多輸入元素要消費時被調用。該函數可以檢查私有狀態對象,並可能發出額外的輸出元素。例如,在輸入元素中搜索特定元素的Gatherer在其完成器被調用時,若未能找到目標元素,可以通過拋出異常等方式報告失敗。
當調用Stream::gather時,執行以下等效步驟:
-
創建一個Downstream對象,當接收到Gatherer輸出類型的元素時,將其傳遞到管道中的下一階段。
-
通過調用其initializer的get()方法獲取Gatherer的私有狀態對象。
-
通過調用其integrator()方法獲取Gatherer的整合器。
-
當存在更多輸入元素時,調用整合器的integrate(...)方法,傳入狀態對象、下一個元素和下游對象。若該方法返回false,則終止處理。
-
獲取Gatherer的完成器並使用狀態對象和下游對象對其調用。
現有Stream介面中聲明的所有中間操作都可以通過調用帶有實現該操作的Gatherer的gather方法來實現。例如,對於一個T類型元素的流,Stream::map通過應用一個函數將每個T元素轉換為U元素並將其向下傳遞;這實質上就是一個無狀態的一對一Gatherer。另一個例子是Stream::filter,它採用一個謂詞決定輸入元素是否應向下傳遞;這隻是一個無狀態的一對多Gatherer。事實上,從概念上講,每一個流管道都可以等同於:
source.gather(...).gather(...).gather(...).collect(...)
Structured Concurrency - JEP 462:
結構化併發。簡化了併發編程。結構化併發將在不同線程中運行的相關任務組視為單個工作單元,從而簡化了錯誤處理和取消,提高了可靠性並增強了可觀察性。
優化:
- 促進一種併發編程風格,能夠消除由於取消和關閉產生的常見風險,例如線程泄露和取消延遲。
- 提升併發代碼的可觀測性。
結構化併發API的主要類是java.util.concurrent
包中的StructuredTaskScope
類。此類允許開發人員將任務結構化為一組併發子任務,並將它們作為一個整體進行協調管理。子任務通過分別創建新線程(fork)併在之後作為一個整體等待它們完成(join)和可能的情況下作為一個整體取消它們。子任務的成功結果或異常將被父任務聚合併處理。StructuredTaskScope
確保了子任務的生命周期被限定在一個清晰的詞法作用域內,在這個作用域內,任務與其子任務的所有交互,包括分叉(fork
)、同步(join
)、取消(cancellation
)、錯誤處理和結果合成都在此發生。
使用StructuredTaskScopes實例:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // 同步兩個子任務
.throwIfFailed(); // 並傳播錯誤信息
// 這裡,兩個子任務都已經成功,所以組合它們的結果
return new Response(user.get(), order.get());
}
}
這裡理解涉及線程的生命周期變得簡單:在所有情況下,它們的生命周期都被限制在一個詞法作用域內,即try-with-resources
語句的主體內。此外,使用StructuredTaskScope
確保了一系列有價值的特性:
-
錯誤處理與短路機制——如果
findUser()
或fetchOrder()
子任務之一失敗,尚未完成的另一個子任務將被取消。(這是由ShutdownOnFailure
實現的關閉策略管理的,也有可能實現其他策略)。 -
取消傳播——如果在調用
join()
之前或期間執行handle()
方法的線程被中斷,則當線程退出作用域時,兩個子任務都將自動取消。 -
清晰性——上述代碼具有清晰的結構:設置子任務,等待它們完成或被取消,然後決定是否成功(並處理已完成子任務的結果)或失敗(子任務已經結束,因此無需進一步清理)。
-
可觀測性——如下麵所述,線程轉儲能夠清晰地顯示任務層級關係,執行
findUser()
和fetchOrder()
的線程在轉儲中顯示為scope的子線程。
Scoped Values - JEP 464:
作用域值優化。線上程內和跨線程之間有效共用不可變數據。這個Java增強它旨在提供一種機制,允許開發者在Java應用程式中安全地線上程內部以及跨線程之間共用不可變數據。該特性旨在替代或改善現有的ThreadLocal機制,提供一種更加可控、易於管理和高效的解決方案,尤其是在涉及大規模併發處理和跨層數據傳遞場景時。通過範圍值,開發人員可以更好地組織和管理在特定作用域內有效的變數,同時確保資源的有效利用和數據的安全共用。
優化:
- 易用性 — 理解數據流應當輕鬆直觀。
- 可理解性 — 共用數據的生命周期可以從代碼的語法結構中清晰可見。
- 健壯性 — 調用者共用的數據只能被合法的被調用者獲取。
- 性能 — 數據能夠有效地在大量線程間高效共用。
作用域值是一種容器對象,允許方法在同一個線程內安全高效地將其數據值與直接和間接的被調用者共用,同時也能與子線程共用,而無需依賴方法參數。它是一個類型為ScopedValue
的變數,通常被聲明為final static
欄位,並設置為private
訪問許可權,以防止其他類的代碼直接訪問。
類似線程局部變數,作用域值關聯了多個值,每個線程對應一個。具體使用的值取決於哪個線程調用了它的方法。不同於線程局部變數,範圍限定值只被寫入一次,並且線上程執行期間只能在一定時間段內可用。
作用域值的使用如下所示。一些代碼調用ScopedValue.where
,提供一個範圍限定值及其要綁定的對象。調用run方法會綁定該範圍限定值,為當前線程提供一個特定的副本,然後執行作為參數傳遞的lambda
表達式。在run方法執行期間,lambda
表達式或從中直接或間接調用的任何方法,都可以通過值的get()
方法讀取範圍限定值。當run方法執行完畢後,該綁定關係會被銷毀。
final static ScopedValue<...> NAME = ScopedValue.newInstance();
// 在某個方法中
ScopedValue.where(NAME, <value>)
.run(() -> { ... NAME.get() ... 調用方法 ... });
// 在lambda表達式中直接或間接調用的方法中
... NAME.get() ...
代碼的結構明確了線程可以讀取其作用域值副本的時間段。這個有限的生命周期極大地簡化了對線程行為的推理。數據從調用者單向傳輸至直接和間接的被調用者,一眼就能看出。不存在能讓遠端代碼隨時改變範圍限定值的set方法。這也有助於提高性能:無論調用者和被調用者的棧距離如何,通過get()方法讀取作用域值的速度常常與讀取局部變數一樣快。
Vector API - JEP 460:
矢量API。一個能夠在支持的CPU架構上運行時可靠編譯為最優矢量指令的API,從而實現優於等效標量計算的性能。
本JEP提議在JDK 22中重新孵化該API,相比於JDK 21版本,API進行了些許增強。實現內容包括bug修複和性能優化。主要變更如下:
支持通過任意原始元素類型的數組支持的堆記憶體MemorySegments
進行矢量訪問。在此之前,訪問僅限於由位元組數組支持的堆記憶體MemorySegments
。
優化:
-
清晰簡潔的API
API應該能夠清晰簡潔地表述一系列由迴圈內矢量操作序列組成的各種矢量計算,可能還包括控制流程。應支持針對矢量大小或每矢量的通道數進行泛型表達,從而使這類計算能夠在支持不同矢量大小的硬體之間移植。 -
平臺無關性
API應獨立於CPU架構,支持在多種支持矢量指令的架構上實現。按照Java API的一貫原則,在平臺優化和可移植性產生衝突時,我們會傾向於使API更具可移植性,即使這意味著某些特定於平臺的慣用法無法在便攜代碼中表達。 -
在x64和AArch64架構上的可靠運行時編譯和高性能
在具備能力的x64架構上,Java運行時環境,特別是HotSpot C2編譯器,應將矢量操作編譯為相應的高效矢量指令,比如Streaming SIMD Extensions (SSE) 和Advanced Vector Extensions (AVX)支持的那些指令。開發者應有信心他們所表達的矢量操作將可靠地緊密映射到相關的矢量指令上。在具備能力的ARM AArch64架構上,C2同樣會將矢量操作編譯為NEON和SVE支持的矢量指令。 -
優雅降級
有時矢量計算可能無法完全在運行時表述為矢量指令序列,可能是因為架構不支持所需的某些指令。在這種情況下,Vector API實現應能夠優雅降級並仍然正常運作。這可能包括在矢量計算無法高效編譯為矢量指令時發出警告。在不支持矢量的平臺上,優雅降級將生成與手動展開迴圈相競爭的代碼,其中展開因數為所選矢量的通道數。 -
與
Project Valhalla
項目的契合
Vector API
的長期目標是利用Project Valhalla
對Java對象模型的增強功能。主要來說,這意味著將Vector API
當前基於值的類更改為值類,以便程式能夠處理值對象,即缺乏對象標識性的類實例。因此,Vector API
將在多個版本中孵化,直至Project Valhalla
的必要特性作為預覽功能可用。一旦這些Valhalla特性可用,我們將調整Vector API及其實現以使用這些特性,並將Vector API
本身提升為預覽功能。
向量由抽象類Vector<E>
表示。類型變數E被實例化為矢量覆蓋的標量基本整數或浮點元素類型的裝箱類型。一個向量還具有形狀屬性,該屬性定義了矢量的大小(以位為單位)。當矢量計算由HotSpot C2
編譯器編譯時,向量的形狀決定了Vector<E>
實例如何映射到硬體矢量寄存器。向量的長度,即車道數或元素個數,等於矢量大小除以元素大小。
支持的一系列元素類型(E)包括Byte、Short、Integer、Long、Float和Double,分別對應於標量基本類型byte、short、int、long、float和double。
支持的一系列形狀對應於64位、128位、256位和512位的矢量大小,以及最大位數。512位形狀可以將位元組打包成64個車道,或者將整數打包成16個車道,具有這種形狀的矢量可以一次性操作64個位元組或16個整數。max-bits形狀支持當前架構的最大矢量尺寸,這使得API能夠支持ARM SVE
平臺,該平臺實現可以支持從128位到2048位,以128位為增量的任何固定尺寸。
性能上的改進
Regional Pinning for G1 - JEP 423:
區域固定。通過在G1中實現區域固定(regional pinning),從而在Java Native Interface (JNI) 臨界區域內不需要禁用垃圾收集,以此來減少延遲。
優化:
- 不會因JNI臨界區域導致線程停滯。
- 不會因JNI臨界區域而導致垃圾收集啟動時增加額外延遲。
- 當沒有JNI臨界區域活動時,GC暫停時間不會出現倒退。
- 當JNI臨界區域活動時,GC暫停時間只會有最小程度的倒退。
工具類
Launch Multi-File Source-Code Programs - JEP 458:
啟動多文件源代碼程式。允許用戶在不首先編譯程式的情況下運行由多個 Java 源代碼文件提供的程式。
優化:
- 通過使從小型程式向大型程式的過渡更加漸進,使開發人員能夠選擇何時以及何時費力地配置構建工具,提高了開發人員的生產力。
Java22增強了Java啟動器的源文件模式,使其能夠運行以多份Java源代碼文件形式提供的程式。
舉例來說,假設一個目錄包含了兩個文件:Prog.java和Helper.java,每個文件各聲明一個類:
// Prog.java
class Prog {
public static void main(String[] args) { Helper.run(); }
}
// Helper.java
class Helper {
static void run() { System.out.println("Hello!"); }
}
運行命令java Prog.java
將會在記憶體中編譯Prog類並調用其main方法。由於Prog類中的代碼引用了Helper類,啟動器會在文件系統中查找Helper.java文件,併在記憶體中編譯Helper類。如果Helper類中的代碼又引用了其他類,例如HelperAux類,那麼啟動器還會找到HelperAux.java並對其進行編譯。
當不同.java文件中的類互相引用時,Java啟動器並不保證按照特定順序或時間點編譯這些.java文件。例如,啟動器可能先編譯Helper.java再編譯Prog.java。有些代碼可能在程式開始執行前就已經被編譯,而其他代碼可能在需要時才懶載入編譯。
只有被程式引用到的類所在的.java文件才會被編譯。這樣一來,開發者可以在嘗試新版本代碼時不必擔心舊版本會被意外編譯。例如,假設目錄中還包含OldProg.java文件,其中包含Progr類的一個舊版本,該版本期望Helper類有一個名為go的方法而不是run方法。當運行Prog.java時,存在包含潛在錯誤的OldProg.java文件並不會影響程式執行。
一個.java文件中可以聲明多個類,且會被一起編譯。在一個.java文件中共聲明的類優先於在其他.java文件中聲明的類。例如,假設上面的Prog.java文件擴展後也在其中聲明瞭Helper類,儘管Helper.java文件中已有一個同名類。當Prog.java中的代碼引用Helper時,會使用在Prog.java中共同聲明的那個類;啟動器不會去搜索Helper.java文件。
源代碼程式中禁止重覆的類聲明。也就是說,同一個.java文件內或構成程式的不同.java文件之間的類聲明,如果名稱相同,則不允許存在。假設經過編輯後,Prog.java和Helper.java最終變成以下形式,其中類Aux意外地在兩個文件中都被聲明:
// Prog.java
class Prog {
public static void main(String[] args) { Helper.run(); Aux.cleanup(); }
}
class Aux {
static void cleanup() { ... }
}
// Helper.java
class Helper {
static void run() { ... }
}
class Aux {
static void cleanup() { ... }
}
運行命令java Prog.java
會編譯Prog.java中的Prog和Aux類,調用Prog類的main方法,然後——由於main方法引用了Helper——查找並編譯Helper.java中的Helper和Aux類。Helper.java中對Aux類的重覆聲明是不允許的,所以程式會停止運行,啟動器報告錯誤。
當通過Java啟動器傳遞單個.java文件名稱時,就會觸發其源文件模式。如果提供了額外的文件名,它們會成為主方法的參數。例如,運行命令java Prog.java Helper.java
會導致字元串數組"Helper.java"作為參數傳給Prog類的main方法。
其他特性
除了JEP中描述的更改之外,發行說明中還列出了許多較小的更新,這些更新對許多應用程式開發者有重要意義。其中包括廢棄過時的API和移除先前已經棄用的API。
- 向keytool和jarsigner添加了更多演算法。
- 垃圾回收器吞吐量方面的改進,特別是在“年輕代”垃圾回收方面。
- 改進了系統模塊描述符的版本報告功能。
- 提高了對原生代碼“等待”處理選項的完善。
- Unicode通用區域數據倉庫已更新至版本44。
- 支持從位元組碼載入的類型上的類型註解。
- ForkJoinPool和ForkJoinTask現在能更好地處理不可中斷任務。
- 對客戶端與伺服器TLS連接屬性配置提供了更多的靈活性。
- 提高了對原生記憶體跟蹤的功能,包括峰值使用情況的報告。
- 最後,如同所有特性發佈版一樣,JDK 22包含了數百項性能、穩定性和安全性更新,包括適應底層操作系統和固件更新及標準變化。用戶和應用程式開發者通常在不知不覺中受益於這些變化。
最後,JDK 22是通過六個月的發佈節奏按時交付的13th功能版本。由於預期改進源源不斷,這種程度的可預測性使開發人員能夠輕鬆管理創新的採用。Oracle 不會為 JDK 22 提供長期支持,在 2023 年 9 月之前提供更新,之後它將被 Oracle JDK 23 取代。最近的長期維護版本是Java 21。
本文已收錄於我的個人博客:碼農Academy的博客,專註分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構設計、面試題、程式員攻略等