DK 17 在 2021 年 9 月 14 號正式釋出了!根據釋出的規劃,這次釋出的 JDK 17 是一個長期維護的版本(LTS)。
Java 17 提供了數千個效能、穩定性和安全性更新,以及 14 個 JEP(JDK 增強提案),進一步改進了 Java 語言和平臺,以幫助開發人員提高工作效率。
JDK 17 包括新的語言增強、庫更新、對新 Apple (Mx CPU)計算機的支援、舊功能的刪除和棄用,並努力確保今天編寫的 Java 程式碼在未來的 JDK 版本中繼續工作而不會發生變化。它還提供語言功能預覽和孵化 API,以收集 Java 社群的反饋
語言特性增強
密封的類和介面(正式版)
封閉類可以是封閉類和或者封閉介面,用來增強 Java 程式語言,防止其他類或介面擴充套件或實現它們。這個特性由Java 15的預覽版本晉升為正式版本。
- 密封的類和介面解釋和應用
因為我們引入了sealed
class
或interfaces
,這些class或者interfaces只允許被指定的類或者interface進行擴充套件和實現。
使用修飾符sealed
,您可以將一個類宣告為密封類。密封的類使用reserved關鍵字permits列出可以直接擴充套件它的類。子類可以是最終的,非密封的或密封的。
之前我們的程式碼是這樣的。
public class Person { } //人
class Teacher extends Person { }//教師
class Worker extends Person { } //工人
class Student extends Person{ } //學生
但是我們現在要限制 Person類 只能被這三個類繼承,不能被其他類繼承,需要這麼做。
// 新增sealed修飾符,permits後面跟上只能被繼承的子類名稱
public sealed class Person permits Teacher, Worker, Student{ } //人
// 子類可以被修飾為 final
final class Teacher extends Person { }//教師
// 子類可以被修飾為 non-sealed,此時 Worker類就成了普通類,誰都可以繼承它
non-sealed class Worker extends Person { } //工人
// 任何類都可以繼承Worker
class AnyClass extends Worker{}
//子類可以被修飾為 sealed,同上
sealed class Student extends Person permits MiddleSchoolStudent,GraduateStudent{ } //學生
final class MiddleSchoolStudent extends Student { } //中學生
final class GraduateStudent extends Student { } //研究生
很強很實用的一個特性,可以限制類的層次結構。
- 補充:它是由Amber專案孵化而來(會經歷兩輪以上預覽版本)
什麼是Amber專案?
Amber 專案的目標是探索和孵化更小的、以生產力為導向的 Java 語言功能,這些功能已被 OpenJDK JEP 流程接受為候選 JEP。本專案由 Compiler Group 贊助。 大多數 Amber 功能在成為 Java 平臺的正式部分之前至少要經過兩輪預覽。對於給定的功能,每輪預覽和最終標準化都有單獨的 JEP。此頁面僅連結到某個功能的最新 JEP。此類 JEP 可能會酌情連結到該功能的早期 JEP。
工具庫的更新
JEP 306:恢復始終嚴格的浮點語義
Java 程式語言和 Java 虛擬機器最初只有嚴格的浮點語義。從 Java 1.2 開始,預設情況下允許在這些嚴格語義中進行微小的變化,以適應當時硬體架構的限制。這些差異不再有幫助或必要,因此已被 JEP 306 刪除。
JEP 356:增強的偽隨機數生成器
為偽隨機數生成器 (PRNG) 提供新的介面型別和實現。這一變化提高了不同 PRNG 的互操作性,並使得根據需求請求演算法變得容易,而不是硬編碼特定的實現。簡單而言只需要理解如下三個問題: @pdai
JDK 17之前如何生成隨機數?
- Random 類
典型的使用如下,隨機一個int值
// random int
new Random().nextInt();
/**
* description 獲取指定位數的隨機數
*
* @param length 1
* @return java.lang.String
*/
public static String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
- ThreadLocalRandom 類
提供執行緒間獨立的隨機序列。它只有一個例項,多個執行緒用到這個例項,也會線上程內部各自更新狀態。它同時也是 Random 的子類,不過它幾乎把所有 Random 的方法又實現了一遍。
/**
* nextInt(bound) returns 0 <= value < bound; repeated calls produce at
* least two distinct results
*/
public void testNextIntBounded() {
// sample bound space across prime number increments
for (int bound = 2; bound < MAX_INT_BOUND; bound += 524959) {
int f = ThreadLocalRandom.current().nextInt(bound);
assertTrue(0 <= f && f < bound);
int i = 0;
int j;
while (i < NCALLS &&
(j = ThreadLocalRandom.current().nextInt(bound)) == f) {
assertTrue(0 <= j && j < bound);
++i;
}
assertTrue(i < NCALLS);
}
}
- SplittableRandom 類
非執行緒安全,但可以 fork 的隨機序列實現,適用於拆分子任務的場景。
/**
* Repeated calls to nextLong produce at least two distinct results
*/
public void testNextLong() {
SplittableRandom sr = new SplittableRandom();
long f = sr.nextLong();
int i = 0;
while (i < NCALLS && sr.nextLong() == f)
++i;
assertTrue(i < NCALLS);
}
為什麼需要增強?
- 上述幾個類實現程式碼質量和介面抽象不佳
- 缺少常見的偽隨機演算法
- 自定義擴充套件隨機數的演算法只能自己去實現,缺少統一的介面
增強後是什麼樣的?
程式碼的最佳化自不必說,我們就看下新增了哪些常見的偽隨機演算法
如何使用這個呢?可以使用RandomGenerator
RandomGenerator g = RandomGenerator.of("L64X128MixRandom");
JEP 382:新的macOS渲染管道
使用 Apple Metal API 為 macOS 實現 Java 2D 管道。新管道將減少 JDK 對已棄用的 Apple OpenGL API 的依賴。
目前預設情況下,這是禁用的,因此渲染仍然使用OpenGL API;要啟用metal,應用程式應透過設定系統屬性指定其使用:
-Dsun.java2d.metal=true
Metal或OpenGL的使用對應用程式是透明的,因為這是內部實現的區別,對Java API沒有影響。Metal管道需要macOS 10.14.x或更高版本。在早期版本上設定它的嘗試將被忽略。
新的平臺支援
JEP 391:支援macOS AArch64
將 JDK 移植到 macOS/AArch64 平臺。該埠將允許 Java 應用程式在新的基於 Arm 64 的 Apple Silicon 計算機上本地執行。
舊功能的刪除和棄用
JEP 398:棄用 Applet API
所有網路瀏覽器供應商要麼已取消對 Java 瀏覽器外掛的支援,要麼已宣佈計劃這樣做。 Applet API 已於 2017 年 9 月在 Java 9 中棄用,但並未移除。
JEP 407:刪除 RMI 啟用
刪除遠端方法呼叫 (RMI) 啟用機制,同時保留 RMI 的其餘部分。
JEP 410:刪除實驗性 AOT 和 JIT 編譯器
實驗性的基於 Java 的提前 (AOT) 和即時 (JIT) 編譯器是實驗性功能,並未得到廣泛採用。作為可選,它們已經從 JDK 16 中刪除。這個 JEP 從 JDK 原始碼中刪除了這些元件。
JEP 411:棄用安全管理器以進行刪除
安全管理器可以追溯到 Java 1.0。多年來,它一直不是保護客戶端 Java 程式碼的主要方法,也很少用於保護伺服器端程式碼。在未來的版本中將其刪除將消除重大的維護負擔,並使 Java 平臺能夠向前發展。
新功能的預覽和孵化API
JEP 406:新增switch模式匹配(預覽版)
允許針對多個模式測試表示式,每個模式都有特定的操作,以便可以簡潔安全地表達複雜的面向資料的查詢。
JEP 412:外部函式和記憶體api (第一輪孵化)
改進了 JDK 14 和 JDK 15 中引入的孵化 API,使 Java 程式能夠與 Java 執行時之外的程式碼和資料進行互操作。透過有效地呼叫外部函式(即 JVM 之外的程式碼)和安全地訪問外部記憶體,這些 API 使 Java 程式能夠呼叫本地庫和處理本地資料,而不會像 Java 本地介面 (JNI) 那樣脆弱和複雜。這些 API 正在巴拿馬專案中開發,旨在改善 Java 和非 Java 程式碼之間的互動。
JEP 414:Vector API(第二輪孵化)
如下內容來源於https://xie.infoq.cn/article/8304c894c4e38318d38ceb116,作者是九叔
AVX(Advanced Vector Extensions,高階向量擴充套件)實際上是 x86-64 處理器上的一套 SIMD(Single Instruction Multiple Data,單指令多資料流)指令集,相對於 SISD(Single instruction, Single dat,單指令流但資料流)而言,SIMD 非常適用於 CPU 密集型場景,因為向量計算允許在同一個 CPU 時鐘週期內對多組資料批次進行資料運算,執行效能非常高效,甚至從某種程度上來看,向量運算似乎更像是一種並行任務,而非像標量計算那樣,在同一個 CPU 時鐘週期內僅允許執行一組資料運算,存在嚴重的執行效率低下問題。
隨著 Java16 的正式來臨,開發人員可以在程式中使用 Vector API 來實現各種複雜的向量計算,由 JIT 編譯器 Server Compiler(C2)在執行期將其編譯為對應的底層 AVX 指令執行。當然,在講解如何使用 Vector API 之前,我們首先來看一個簡單的標量計算程式。示例:
void scalarComputation() {
var a = new float[10000000];
var b = new float[10000000];
// 省略陣列a和b的賦值操作
var c = new float[10000000];
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
在上述程式示例中,迴圈體內每次只能執行一組浮點運算,總共需要執行約 1000 萬次才能夠獲得最終的運算結果,可想而知,這樣的執行效率必然低效。值得慶幸的是,從 Java6 的時代開始,Java 的設計者們就在 HotSpot 虛擬機器中引入了一種被稱之為 SuperWord 的自動向量最佳化演算法,該演算法預設會將迴圈體內的標量計算自動最佳化為向量計算,以此來提升資料運算時的執行效率。當然,我們可以透過虛擬機器引數-XX:-UseSuperWord來顯式關閉這項最佳化(從實際測試結果來看,如果不開啟自動向量最佳化,存在約 20%~22%之間的效能下降)。
在此大家需要注意,儘管 HotSpot 預設支援自動向量最佳化,但侷限性仍然非常明顯,首先,JIT 編譯器 Server Compiler(C2)僅僅只會對迴圈體內的程式碼塊做向量最佳化,並且這樣的最佳化也是極不可靠的;其次,對於一些複雜的向量運算,SuperWord 則顯得無能為力。因此,在一些特定場景下(比如:機器學習,線性代數,密碼學等),建議大家還是儘可能使用 Java16 為大家提供的 Vector API 來實現複雜的向量計算。示例:
// 定義256bit的向量浮點運算
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
var i = 0;
var upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
值得注意的是,Vector API 包含在 jdk.incubator.vector 模組中,程式中如果需要使用 Vector API 則需要在 module-info.java 檔案中引入該模組。:
module java16.test{
requires jdk.incubator.vector;
}
JEP 389:外部連結器 API(孵化器)
該孵化器 API 提供了靜態型別、純 Java 訪問原生程式碼的特性,該 API 將大大簡化繫結原生庫的原本複雜且容易出錯的過程。Java 1.1 就已透過 Java 原生介面(JNI)支援了原生方法呼叫,但並不好用。Java 開發人員應該能夠為特定任務繫結特定的原生庫。它還提供了外來函式支援,而無需任何中間的 JNI 粘合程式碼。
JEP 393:外部儲存器訪問 API(第三次孵化)
在 Java 14 和 Java 15 中作為孵化器 API 引入的這個 API 使 Java 程式能夠安全有效地對各種外部儲存器(例如本機儲存器、永續性儲存器、託管堆儲存器等)進行操作。它提供了外部連結器 API 的基礎。
如下內容來源於https://xie.infoq.cn/article/8304c894c4e38318d38ceb116,作者是九叔
在實際的開發過程中,絕大多數的開發人員基本都不會直接與堆外記憶體打交道,但這並不代表你從未接觸過堆外記憶體,像大家經常使用的諸如:RocketMQ、MapDB 等中介軟體產品底層實現都是基於堆外儲存的,換句話說,我們幾乎每天都在間接與堆外記憶體打交道。那麼究竟為什麼需要使用到堆外記憶體呢?簡單來說,主要是出於以下 3 個方面的考慮:
- 減少 GC 次數和降低 Stop-the-world 時間;
- 可以擴充套件和使用更大的記憶體空間;
- 可以省去實體記憶體和堆記憶體之間的資料複製步驟。
在 Java14 之前,如果開發人員想要操作堆外記憶體,通常的做法就是使用 ByteBuffer 或者 Unsafe,甚至是 JNI 等方式,但無論使用哪一種方式,均無法同時有效解決安全性和高效性等 2 個問題,並且,堆外記憶體的釋放也是一個令人頭痛的問題。以 DirectByteBuffer 為例,該物件僅僅只是一個引用,其背後還關聯著一大段堆外記憶體,由於 DirectByteBuffer 物件例項仍然是儲存在堆空間內,只有當 DirectByteBuffer 物件被 GC 回收時,其背後的堆外記憶體才會被進一步釋放。
在此大家需要注意,程式中透過 ByteBuffer.allocateDirect()方法來申請實體記憶體資源所耗費的成本遠遠高於直接在 on-heap 中的操作,而且實際開發過程中還需要考慮資料結構如何設計、序列化/反序列化如何支撐等諸多難題,所以與其使用語法層面的 API 倒不如直接使用 MapDB 等開源產品來得更實惠。
如今,在堆外記憶體領域,我們似乎又多了一個選擇,從 Java14 開始,Java 的設計者們在語法層面為大家帶來了嶄新的 Memory Access API,極大程度上簡化了開發難度,並得以有效的解決了安全性和高效性等 2 個核心問題。示例:
// 獲取記憶體訪問var控制程式碼
var handle = MemoryHandles.varHandle(char.class,
ByteOrder.nativeOrder());
// 申請200位元組的堆外記憶體
try (MemorySegment segment = MemorySegment.allocateNative(200)) {
for (int i = 0; i < 25; i++) {
handle.set(segment, i << 2, (char) (i + 1 + 64));
System.out.println(handle.get(segment, i << 2));
}
}
關於堆外記憶體段的釋放,Memory Access API 提供有顯式和隱式 2 種方式,開發人員除了可以在程式中透過 MemorySegment 的 close()方法來顯式釋放所申請的記憶體資源外,還可以註冊 Cleaner 清理器來實現資源的隱式釋放,後者會在 GC 確定目標記憶體段不再可訪問時,釋放與之關聯的堆外記憶體資源。
參考文章
-
https://www.oracle.com/news/announcement/oracle-releases-java-17-2021-09-14/
-
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html