一、導語 幾天前Oracle剛剛發佈了Java21, 由於這是最新的LTS版本,引起了大家的關註。 我也第一時間在個人項目中進行了升級體驗。 一探究竟,和大家分享。 二、Java21更新內容介紹 官方release公告: https://jdk.java.net/21/release-notes 開 ...
一、導語
幾天前Oracle剛剛發佈了Java21,
由於這是最新的LTS版本,引起了大家的關註。
我也第一時間在個人項目中進行了升級體驗。
一探究竟,和大家分享。
二、Java21更新內容介紹
官方release公告:
https://jdk.java.net/21/release-notes
開源中國介紹:
https://my.oschina.net/waylau/blog/10112170
新特性一覽:
-
JEP 431:序列集合
-
JEP 439:分代 ZGC
-
JEP 440:記錄模式
-
JEP 441:switch 模式匹配
-
JEP 444:虛擬線程
-
JEP 449:棄用 Windows 32 位 x86 移植
-
JEP 451:準備禁止動態載入代理
-
JEP 452:密鑰封裝機制 API
-
JEP 430:字元串模板(預覽)
-
JEP 442:外部函數和記憶體 API(第三次預覽)
-
JEP 443:未命名模式和變數(預覽)
-
JEP 445:未命名類和實例主方法(預覽)
-
JEP 446:作用域值(預覽)
-
JEP 453:結構化併發(預覽)
-
JEP 448:Vector API(孵化器第六階段)
其中大家比較關註的是分代 ZGC和虛擬線程。
三、開箱
下載地址:
OpenJDK 版本:https://jdk.java.net/21/
Oracle 版本:https://www.oracle.com/java/technologies/downloads/
對比17
邊框由不鏽鋼升級為鈦金屬
目錄結構一致:
模塊數量比17少一個:
整體大小從289MB增加到了320MB
四、升級體驗
下載
更新pom
嘗試運行
運行報錯:
java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
解決辦法:
升級lombok至1.18.30
相容性檢查:
由於我的項目以前用的JDK17,本次升級相容性良好,只發現了一處:
系統托盤中 使用了PopupMenu,出現了字元集問題:
五、分代ZGC體驗
ZGC在之前的JDK版本中也有,這次的分代ZGC更是被大家看好,官方的介紹如下:
Applications running with Generational ZGC should enjoy:
Lower risks of allocations stalls,
Lower required heap memory overhead, and
Lower garbage collection CPU overhead.
Enable Generational ZGC with command line options -XX:+UseZGC -XX:+ZGenerational
性能測試參考:
JVM參數:-XX:+UseZGC -XX:+ZGenerational
使用Java21,未使用ZGC
MooInfo記憶體占用查看
使用Java21,使用分代ZGC
MooInfo記憶體占用查看
以上只是初步體驗,關於ZGC的更多內容,如詳細的分代回收情況後續進一步探索。
以上記憶體占用查看使用我之前做的一個工具,MooInfo:
https://github.com/rememberber/MooInfo
六、虛擬線程探索
Virtual threads are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications.
虛擬線程是輕量級線程,可以減少編寫、維護和調試高吞吐量併發應用程式的工作量。
Oracle介紹原文:
平臺線程
Oracle官方文檔的機器翻譯:
平臺線程是作為操作系統(OS)線程的瘦包裝器實現的。
平臺線程在其底層操作系統線程上運行Java代碼,平臺線程在平臺線程的整個生命周期內捕獲其操作系統線程。
因此,可用平臺線程的數量受限於操作系統線程的數量。
平臺線程通常有一個大的線程堆棧和其他由操作系統維護的資源。
平臺線程支持線程局部變數。
平臺線程適合運行所有類型的任務,但可能是有限的資源。
虛擬線程
Oracle官方文檔的機器翻譯:
與平臺線程一樣,虛擬線程也是 java.lang.Thread 的一個實例。
但是,虛擬線程並不依賴於特定的操作系統線程。
虛擬線程仍然在操作系統線程上運行代碼。
但是,當虛擬線程中運行的代碼調用阻塞 I/O 操作時,Java 運行時會掛起虛擬線程,直到可以恢復為止。
與掛起的虛擬線程關聯的操作系統線程現在可以自由地為其他虛擬線程執行操作。
實現原理
虛擬線程的實現方式與虛擬記憶體類似。
為了模擬大量記憶體,操作系統將較大的虛擬地址空間映射到有限的 RAM。
同樣,為了模擬大量線程,Java運行時將大量虛擬線程映射到少量操作系統線程。
與平臺線程不同,虛擬線程通常具有淺調用堆棧,只執行單個 HTTP 客戶端調用或單個 JDBC 查詢。
儘管虛擬線程支持線程局部變數,但您應該仔細考慮使用它們,因為單個 JVM 可能支持數百萬個虛擬線程。
虛擬線程適合運行大部分時間處於阻塞狀態、通常等待 I/O 操作完成的任務。
但是,它們不適用於長時間運行的 CPU 密集型操作。
虛擬線程用法
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();
或者
try {
Thread.Builder builder = Thread.ofVirtual().name("MyThread");
Runnable task = () -> {
System.out.println("Running thread");
};
Thread t = builder.start(task);
System.out.println("Thread t name: " + t.getName());
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
或者
public class CreateNamedThreadsWithBuilders {
public static void main(String[] args) {
try {
Thread.Builder builder =
Thread.ofVirtual().name("worker-", 0);
Runnable task = () -> {
System.out.println("Thread ID: " +
Thread.currentThread().threadId());
};
// name "worker-0"
Thread t1 = builder.start(task);
t1.join();
System.out.println(t1.getName() + " terminated");
// name "worker-1"
Thread t2 = builder.start(task);
t2.join();
System.out.println(t2.getName() + " terminated");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
或者
try (ExecutorService myExecutor =
Executors.newVirtualThreadPerTaskExecutor()) {
Future<?> future =
myExecutor.submit(() -> System.out.println("Running thread"));
future.get();
System.out.println("Task completed");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
以上是Java20文檔的用法,實際使用時我發現還可以這樣:
Thread.startVirtualThread(() -> {
// do something
});
平臺線程和虛擬線程對比測試
為了測試對比,我建了一個項目
初步對比,和官網描述一致,計算密集型場景差別不大,IO密集型場景有明顯改善:
虛擬線程100個,IO讀文件
平臺線程100個,IO讀文件
虛擬線程100個,Get請求百度首頁
平臺線程100個,Get請求百度首頁
但是由於是本地測試,且用例比較簡陋,無法完全得出準確結論。
日後大家有實際IO密集性多線程場景可以實際感受下。
線程池?忘了它吧
開發人員通常會將應用程式代碼從基於線程池的傳統 ExecutorService 遷移到虛擬線程每任務 ExecutorService。
線程池和所有資源池一樣,旨在共用昂貴的資源,
但虛擬線程並不昂貴,而且永遠不需要將它們池化。
七、一顆語法糖?Java21 新特性:Record Patterns
一個例子感受一下新特性:Record Patterns
before:
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
after:
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
參考:https://my.oschina.net/didispace/blog/10112428
作者:京東科技 周波
來源:京東雲開發者社區 轉載請註明來源