七、Java 14 新特性
七、Java 14 新特性
Java 14 已如期于 2020 年 3 月 17 日正式發(fā)布,此次更新是繼半年前 Java 13 這大版本發(fā)布之后的又一次常規(guī)版本更新,即便在全球疫情如此嚴峻形勢下,依然保持每六個月的版本更新頻率,為大家及時帶來改進和增強,這一點值得點贊。在這一版中,主要帶來了 ZGC 增強、instanceof 增強、Switch 表達式更新為標準版等方面的改動、增強和新功能。本文主要介紹 Java 14 中的主要新特性,帶您快速了解 Java 14 帶來了哪些不一樣的體驗和便利。
1、知識體系
2、語言特性增強
1、JEP 359: Switch 表達式(正式版)
switch 表達式在之前的 Java 12 和 Java 13 中都是處于預(yù)覽階段,而在這次更新的 Java 14 中,終于成為穩(wěn)定版本,能夠正式可用。
switch 表達式帶來的不僅僅是編碼上的簡潔、流暢,也精簡了 switch 語句的使用方式,同時也兼容之前的 switch 語句的使用;之前使用 switch 語句時,在每個分支結(jié)束之前,往往都需要加上 break 關(guān)鍵字進行分支跳出,以防 switch 語句一直往后執(zhí)行到整個 switch 語句結(jié)束,由此造成一些意想不到的問題。switch 語句一般使用冒號 :來作為語句分支代碼的開始,而 switch 表達式則提供了新的分支切換方式,即 -> 符號右則表達式方法體在執(zhí)行完分支方法之后,自動結(jié)束 switch 分支,同時 -> 右則方法塊中可以是表達式、代碼塊或者是手動拋出的異常。以往的 switch 語句寫法如下:
清單 9. Switch 語句
int dayOfWeek; switch (day) {case MONDAY:case FRIDAY:case SUNDAY:dayOfWeek = 6;break;case TUESDAY:dayOfWeek = 7;break;case THURSDAY:case SATURDAY:dayOfWeek = 8;break;case WEDNESDAY:dayOfWeek = 9;break;default:dayOfWeek = 0;break; }而現(xiàn)在 Java 14 可以使用 switch 表達式正式版之后,上面語句可以轉(zhuǎn)換為下列寫法:
清單 10. Switch 表達式
int dayOfWeek = switch (day) {case MONDAY, FRIDAY, SUNDAY -> 6;case TUESDAY -> 7;case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9;default -> 0;};很明顯,switch 表達式將之前 switch 語句從編碼方式上簡化了不少,但是還是需要注意下面幾點:
- 需要保持與之前 switch 語句同樣的 case 分支情況。
- 之前需要用變量來接收返回值,而現(xiàn)在直接使用 yield 關(guān)鍵字來返回 case 分支需要返回的結(jié)果。
- 現(xiàn)在的 switch 表達式中不再需要顯式地使用 return、break 或者 continue 來跳出當前分支。
- 現(xiàn)在不需要像之前一樣,在每個分支結(jié)束之前加上 break 關(guān)鍵字來結(jié)束當前分支,如果不加,則會默認往后執(zhí)行,直到遇到 break 關(guān)鍵字或者整個 switch 語句結(jié)束,在 Java 14 表達式中,表達式默認執(zhí)行完之后自動跳出,不會繼續(xù)往后執(zhí)行。
- 對于多個相同的 case 方法塊,可以將 case 條件并列,而不需要像之前一樣,通過每個 case 后面故意不加 break 關(guān)鍵字來使用相同方法塊。
使用 switch 表達式來替換之前的 switch 語句,確實精簡了不少代碼,提高了編碼效率,同時也可以規(guī)避一些可能由于不太經(jīng)意而出現(xiàn)的意想不到的情況,可見 Java 在提高使用者編碼效率、編碼體驗和簡化使用方面一直在不停的努力中,同時也期待未來有更多的類似 lambda、switch 表達式這樣的新特性出來。
3、新功能和庫的更新
1、JEP 358: 改進 NullPointerExceptions 提示信息
Java 14 改進 NullPointerException 的可查性、可讀性,能更準確地定位 null 變量的信息。該特性能夠幫助開發(fā)者和技術(shù)支持人員提高生產(chǎn)力,以及改進各種開發(fā)工具和調(diào)試工具的質(zhì)量,能夠更加準確、清楚地根據(jù)動態(tài)異常與程序代碼相結(jié)合來理解程序。
相信每位開發(fā)者在實際編碼過程中都遇到過 NullPointerException,每當遇到這種異常的時候,都需要根據(jù)打印出來的詳細信息來分析、定位出現(xiàn)問題的原因,以在程序代碼中規(guī)避或解決。例如,假設(shè)下面代碼出現(xiàn)了一個 NullPointerException:
book.id = 99;打印出來的 NullPointerException 信息如下:
清單 4. NullPointerException 信息
Exception in thread "main" java.lang.NullPointerExceptionat Book.main(Book.java:5)像上面這種異常,因為代碼比較簡單,并且異常信息中也打印出來了行號信息,開發(fā)者可以很快速定位到出現(xiàn)異常位置:book 為空而導致的 NullPointerException,而對于一些復(fù)雜或者嵌套的情況下出現(xiàn) NullPointerException 時,僅根據(jù)打印出來的信息,很難判斷實際出現(xiàn)問題的位置,具體見下面示例:
shoopingcart.buy.book.id = 99;對于這種比較復(fù)雜的情況下,僅僅單根據(jù)異常信息中打印的行號,則比較難判斷出現(xiàn) NullPointerException 的原因。
而 Java 14 中,則做了對 NullPointerException 打印異常信息的改進增強,通過分析程序的字節(jié)碼信息,能夠做到準確的定位到出現(xiàn) NullPointerException 的變量,并且根據(jù)實際源代碼打印出詳細異常信息,對于上述示例,打印信息如下:
清單 5. NullPointerException 詳細信息
Exception in thread "main" java.lang.NullPointerException: Cannot assign field "book" because "shoopingcart.buy" is nullat Book.main(Book.java:5)對比可以看出,改進之后的 NullPointerException 信息,能夠準確打印出具體哪個變量導致的 NullPointerException,減少了由于僅帶行號的異常提示信息帶來的困惑。該改進功能可以通過如下參數(shù)開啟:
-XX:+ShowCodeDetailsInExceptionMessages該增強改進特性,不僅適用于屬性訪問,還適用于方法調(diào)用、數(shù)組訪問和賦值等有可能會導致 NullPointerException 的地方。
4、舊功能的刪除和棄用
1、JEP 367: 刪除 pack200 和 unpack200 工具
刪除 pack200 和 unpack200 工具,以及 java.util.jar 包中的 Pack200 API。這些工具和 API 在 Java SE 11 中已被棄用,以便在未來的版本中刪除它們。
5、JVM 相關(guān)
1、JEP 345: G1 的 NUMA 可識別內(nèi)存分配
Java 14 改進非一致性內(nèi)存訪問(NUMA)系統(tǒng)上的 G1 垃圾收集器的整體性能,主要是對年輕代的內(nèi)存分配進行優(yōu)化,從而提高 CPU 計算過程中內(nèi)存訪問速度。
NUMA 是 non-unified memory access 的縮寫,主要是指在當前的多插槽物理計算機體系中,比較普遍是多核的處理器,并且越來越多的具有 NUMA 內(nèi)存訪問體系結(jié)構(gòu),即內(nèi)存與每個插槽或內(nèi)核之間的距離并不相等。同時套接字之間的內(nèi)存訪問具有不同的性能特征,對更遠的套接字的訪問通常具有更多的時間消耗。這樣每個核對于每一塊或者某一區(qū)域的內(nèi)存訪問速度會隨著核和物理內(nèi)存所在的位置的遠近而有不同的時延差異。
Java 中,堆內(nèi)存分配一般發(fā)生在線程運行的時候,當創(chuàng)建了一個新對象時,該線程會觸發(fā) G1 去分配一塊內(nèi)存出來,用來存放新創(chuàng)建的對象,在 G1 內(nèi)存體系中,其實就是一塊 region(大對象除外,大對象需要多個 region),在這個分配新內(nèi)存的過程中,如果支持了 NUMA 感知內(nèi)存分配,將會優(yōu)先在與當前線程所綁定的 NUMA 節(jié)點空閑內(nèi)存區(qū)域來執(zhí)行 allocate 操作,同一線程創(chuàng)建的對象,盡可能的保留在年輕代的同一 NUMA 內(nèi)存節(jié)點上,因為是基于同一個線程創(chuàng)建的對象大部分是短存活并且高概率互相調(diào)用的。
具體啟用方式可以在 JVM 參數(shù)后面加上如下參數(shù):
-XX:+UseNUMA通過這種方式來啟用可識別的內(nèi)存分配方式,能夠提高一些大型計算機的 G1 內(nèi)存分配回收性能。
2、JEP 363: 刪除 CMS 垃圾回收器
CMS 是老年代垃圾回收算法,通過標記-清除的方式進行內(nèi)存回收,在內(nèi)存回收過程中能夠與用戶線程并行執(zhí)行。CMS 回收器可以與 Serial 回收器和 Parallel New 回收器搭配使用,CMS 主要通過并發(fā)的方式,適當減少系統(tǒng)的吞吐量以達到追求響應(yīng)速度的目的,比較適合在追求 GC 速度的服務(wù)器上使用。
因為 CMS 回收算法在進行 GC 回收內(nèi)存過程中是使用并行方式進行的,如果服務(wù)器 CPU 核數(shù)不多的情況下,進行 CMS 垃圾回收有可能造成比較高的負載。同時在 CMS 并行標記和并行清理時,應(yīng)用線程還在繼續(xù)運行,程序在運行過程中自然會創(chuàng)建新對象、釋放不用對象,所以在這個過程中,會有新的不可達內(nèi)存地址產(chǎn)生,而這部分的不可達內(nèi)存是出現(xiàn)在標記過程結(jié)束之后,本輪 CMS 回收無法在周期內(nèi)將它們回收掉,只能留在下次垃圾回收周期再清理掉。這樣的垃圾就叫做浮動垃圾。由于垃圾收集和用戶線程是并發(fā)執(zhí)行的,因此 CMS 回收器不能像其他回收器那樣進行內(nèi)存回收,需要預(yù)留一些空間用來保存用戶新創(chuàng)建的對象。由于 CMS 回收器在老年代中使用標記-清除的內(nèi)存回收策略,勢必會產(chǎn)生內(nèi)存碎片,內(nèi)存當碎片過多時,將會給大對象分配帶來麻煩,往往會出現(xiàn)老年代還有空間但不能再保存對象的情況。
所以,早在幾年前的 Java 9 中,就已經(jīng)決定放棄使用 CMS 回收器了,而這次在 Java 14 中,是繼之前 Java 9 中放棄使用 CMS 之后,徹底將其禁用,并刪除與 CMS 有關(guān)的選項,同時清除與 CMS 有關(guān)的文檔內(nèi)容,至此曾經(jīng)輝煌一度的 CMS 回收器,也將成為歷史。
當在 Java 14 版本中,通過使用參數(shù): -XX:+UseConcMarkSweepGC,嘗試使用 CMS 時,將會收到下面信息:
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \ support was removed in <version>3、JEP 364&365: ZGC 支持 MacOS 和 Windows 系統(tǒng)(實驗階段)
ZGC 是最初在 Java 11 中引入,同時在后續(xù)幾個版本中,不斷進行改進的一款基于內(nèi)存 Region,同時使用了內(nèi)存讀屏障、染色指針和內(nèi)存多重映射等技,并且以可伸縮、低延遲為目標的內(nèi)存垃圾回收器器,不過在 Java 14 之前版本中,僅僅只支持在 Linux/x64 位平臺。
此次 Java 14,同時支持 MacOS 和 Windows 系統(tǒng),解決了開發(fā)人員需要在桌面操作系統(tǒng)中使用 ZGC 的問題。
在 MacOS 和 Windows 下面開啟 ZGC 的方式,需要添加如下 JVM 參數(shù):
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC4、JEP 366: 棄用 ParallelScavenge 和 SerialOld GC 的組合使用
由于 Parallel Scavenge 和 Serial Old 垃圾收集算法組合起來使用的情況比較少,并且在年輕代中使用并行算法,而在老年代中使用串行算法,這種并行、串行混搭使用的情況,本身已屬罕見同時也很冒險。由于這兩 GC 算法組合很少使用,卻要花費巨大工作量來進行維護,所以在 Java 14 版本中,考慮將這兩 GC 的組合棄用。
具體棄用情況如下,通過棄用組合參數(shù):-XX:+UseParallelGC -XX:-UseParallelOldGC,來棄用年輕代、老年期中并行、串行混搭使用的情況;同時,對于單獨使用參數(shù):-XX:-UseParallelOldGC 的地方,也將顯示該參數(shù)已被棄用的警告信息。
6、新功能的預(yù)覽和實驗
1、JEP 305: instanceof 模式匹配(預(yù)覽階段)
Java 14 中對 instanceof 的改進,主要目的是為了讓創(chuàng)建對象更簡單、簡潔和高效,并且可讀性更強、提高安全性。
在以往實際使用中,instanceof 主要用來檢查對象的類型,然后根據(jù)類型對目標對象進行類型轉(zhuǎn)換,之后進行不同的處理、實現(xiàn)不同的邏輯,具體可以參考清單 1:
清單 1. instanceof 傳統(tǒng)使用方式
if (person instanceof Student) {Student student = (Student) person;student.say();// other student operations } else if (person instanceof Teacher) {Teacher teacher = (Teacher) person;teacher.say();// other teacher operations }上述代碼中,我們首先需要對 person 對象進行類型判斷,判斷 person 具體是 Student 還是 Teacher,因為這兩種角色對應(yīng)不同操作,亦即對應(yīng)到的實際邏輯實現(xiàn),判斷完 person 類型之后,然后強制對 person 進行類型轉(zhuǎn)換為局部變量,以方便后續(xù)執(zhí)行屬于該角色的特定操作。
上面這種寫法,有下面兩個問題:
- 每次在檢查類型之后,都需要強制進行類型轉(zhuǎn)換。
- 類型轉(zhuǎn)換后,需要提前創(chuàng)建一個局部變量來接收轉(zhuǎn)換后的結(jié)果,代碼顯得多余且繁瑣。
Java 14 中,對 instanceof 進行模式匹配改進之后,上面示例代碼可以改寫成:
清單 2. instanceof 模式匹配使用方式
if (person instanceof Student student) {student.say();// other student operations } else if (person instanceof Teacher teacher) {teacher.say();// other teacher operations }清單 2 中,首先在 if 代碼塊中,對 person 對象進行類型匹配,校驗 person 對象是否為 Student 類型,如果類型匹配成功,則會轉(zhuǎn)換為 Student 類型,并賦值給模式局部變量 student,并且只有當模式匹配表達式匹配成功是才會生效和復(fù)制,同時這里的 student 變量只能在 if 塊中使用,而不能在 else if/else 中使用,否則會報編譯錯誤。
注意,如果 if 條件中有 && 運算符時,當 instanceof 類型匹配成功,模式局部變量的作用范圍也可以相應(yīng)延長,如下面代碼:
清單 3. Instanceof 模式匹配 && 方式
if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}另外,需要注意,這種作用范圍延長,并不適用于或 || 運算符,因為即便 || 運算符左邊的 instanceof 類型匹配沒有成功也不會造成短路,依舊會執(zhí)行到||運算符右邊的表達式,但是此時,因為 instanceof 類型匹配沒有成功,局部變量并未定義賦值,此時使用會產(chǎn)生問題。
與傳統(tǒng)寫法對比,可以發(fā)現(xiàn)模式匹配不但提高了程序的安全性、健壯性,另一方面,不需要顯式的去進行二次類型轉(zhuǎn)換,減少了大量不必要的強制類型轉(zhuǎn)換。模式匹配變量在模式匹配成功之后,可以直接使用,同時它還被限制了作用范圍,大大提高了程序的簡潔性、可讀性和安全性。instanceof 的模式匹配,為 Java 帶來的有一次便捷的提升,能夠剔除一些冗余的代碼,寫出更加簡潔安全的代碼,提高碼代碼效率。
2、JEP 359: Record 類型(預(yù)覽功能)
Java 14 富有建設(shè)性地將 Record 類型作為預(yù)覽特性而引入。Record 類型允許在代碼中使用緊湊的語法形式來聲明類,而這些類能夠作為不可變數(shù)據(jù)類型的封裝持有者。Record 這一特性主要用在特定領(lǐng)域的類上;與枚舉類型一樣,Record 類型是一種受限形式的類型,主要用于存儲、保存數(shù)據(jù),并且沒有其它額外自定義行為的場景下。
在以往開發(fā)過程中,被當作數(shù)據(jù)載體的類對象,在正確聲明定義過程中,通常需要編寫大量的無實際業(yè)務(wù)、重復(fù)性質(zhì)的代碼,其中包括:構(gòu)造函數(shù)、屬性調(diào)用、訪問以及 equals() 、hashCode()、toString() 等方法,因此在 Java 14 中引入了 Record 類型,其效果有些類似 Lombok 的 @Data 注解、Kotlin 中的 data class,但是又不盡完全相同,它們的共同點都是類的部分或者全部可以直接在類頭中定義、描述,并且這個類只用于存儲數(shù)據(jù)而已。對于 Record 類型,具體可以用下面代碼來說明:
清單 6. Record 類型定義
public record Person(String name, int age) {public static String address;public String getName() {return name;} }對上述代碼進行編譯,然后反編譯之后可以看到如下結(jié)果:
清單 7. Record 類型反編譯結(jié)果
public final class Person extends java.lang.Record {private final java.lang.String name;private final java.lang.String age;public Person(java.lang.String name, java.lang.String age) { /* compiled code */ }public java.lang.String getName() { /* compiled code */ }public java.lang.String toString() { /* compiled code */ }public final int hashCode() { /* compiled code */ }public final boolean equals(java.lang.Object o) { /* compiled code */ }public java.lang.String name() { /* compiled code */ }public java.lang.String age() { /* compiled code */ } }根據(jù)反編譯結(jié)果,可以得出,當用 Record 來聲明一個類時,該類將自動擁有下面特征:
- 擁有一個構(gòu)造方法
- 獲取成員屬性值的方法:name()、age()
- hashCode() 方法和 euqals() 方法
- toString() 方法
- 類對象和屬性被 final 關(guān)鍵字修飾,不能被繼承,類的示例屬性也都被 final 修飾,不能再被賦值使用。
- 還可以在 Record 聲明的類中定義靜態(tài)屬性、方法和示例方法。注意,不能在 Record 聲明的類中定義示例字段,類也不能聲明為抽象類等。
可以看到,該預(yù)覽特性提供了一種更為緊湊的語法來聲明類,并且可以大幅減少定義類似數(shù)據(jù)類型時所需的重復(fù)性代碼。
另外 Java 14 中為了引入 Record 這種新的類型,在 java.lang.Class 中引入了下面兩個新方法:
清單 8. Record 新引入至 Class 中的方法
RecordComponent[] getRecordComponents() boolean isRecord()其中 getRecordComponents() 方法返回一組 java.lang.reflect.RecordComponent 對象組成的數(shù)組,java.lang.reflect.RecordComponent也是一個新引入類,該數(shù)組的元素與 Record 類中的組件相對應(yīng),其順序與在記錄聲明中出現(xiàn)的順序相同,可以從該數(shù)組中的每個 RecordComponent 中提取到組件信息,包括其名稱、類型、泛型類型、注釋及其訪問方法。
而 isRecord() 方法,則返回所在類是否是 Record 類型,如果是,則返回 true。
3、JEP 368: 文本塊(第二預(yù)覽版本)
Java 13 引入了文本塊來解決多行文本的問題,文本塊主要以三重雙引號開頭,并以同樣的以三重雙引號結(jié)尾終止,它們之間的任何內(nèi)容都被解釋為文本塊字符串的一部分,包括換行符,避免了對大多數(shù)轉(zhuǎn)義序列的需要,并且它仍然是普通的 java.lang.String 對象,文本塊可以在 Java 中能夠使用字符串的任何地方進行使用,而與編譯后的代碼沒有區(qū)別,還增強了 Java 程序中的字符串可讀性。并且通過這種方式,可以更直觀地表示字符串,可以支持跨越多行,而且不會出現(xiàn)轉(zhuǎn)義的視覺混亂,將可以廣泛提高 Java 類程序的可讀性和可寫性。
Java 14 在 Java 13 引入的文本塊的基礎(chǔ)之上,新加入了兩個轉(zhuǎn)義符,分別是:\ 和 \s,這兩個轉(zhuǎn)義符分別表達涵義如下:
- \:行終止符,主要用于阻止插入換行符;
- \s:表示一個空格。可以用來避免末尾的白字符被去掉。
在 Java 13 之前,多行字符串寫法為:
清單 11. 多行字符串寫法
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +"elit, sed do eiusmod tempor incididunt ut labore " +"et dolore magna aliqua.";在 Java 14 新引入兩個轉(zhuǎn)義符之后,上述內(nèi)容可以寫為:
清單 12. 多行文本塊加上轉(zhuǎn)義符的寫法
String text = """Lorem ipsum dolor sit amet, consectetur adipiscing \elit, sed do eiusmod tempor incididunt ut labore \et dolore magna aliqua.\""";上述兩種寫法,text 實際還是只有一行內(nèi)容。
對于轉(zhuǎn)義符:\s,用法如下,能夠保證下列文本每行正好都是六個字符長度:
清單 13. 多行文本塊加上轉(zhuǎn)義符的寫法
String colors = """red \sgreen\sblue \s""";Java 14 帶來的這兩個轉(zhuǎn)義符,能夠簡化跨多行字符串編碼問題,通過轉(zhuǎn)義符,能夠避免對換行等特殊字符串進行轉(zhuǎn)移,從而簡化代碼編寫,同時也增強了使用 String 來表達 HTML、XML、SQL 或 JSON 等格式字符串的編碼可讀性,且易于維護。
同時 Java 14 還對 String 進行了方法擴展:
- stripIndent() :用于從文本塊中去除空白字符
- translateEscapes():用于翻譯轉(zhuǎn)義字符
- formatted(Object... args):用于格式化
4、JEP 343: 打包工具(孵化器版本)
創(chuàng)建用于打包自包含 Java 應(yīng)用程序的工具。
它基于 JavaFX javapackager 工具創(chuàng)建一個簡單的打包工具,主要目標是:
- 支持原生打包格式,為最終用戶提供自然的安裝體驗。這些格式包括 Windows 上的 msi 和 exe,macOS 上的 pkg 和 dmg,以及 Linux 上的 deb 和 rpm。
- 允許在打包時指定啟動時間參數(shù)。
- 可以從命令行直接調(diào)用,也可以通過 ToolProvider API 以編程方式調(diào)用。
5、JEP 370: 外部存儲器訪問 API(孵化器版)
外存訪問 API(二次孵化),可以允許 Java 應(yīng)用程序安全有效地訪問 Java 堆之外的外部內(nèi)存。目的是引入一個 API,以允許 Java 程序安全、有效地訪問 Java 堆之外的外部存儲器。如本機、持久和托管堆。如下內(nèi)容來源于https://xie.infoq.cn/article/8304c894c4e38318d38ceb116
在實際的開發(fā)過程中,絕大多數(shù)的開發(fā)人員基本都不會直接與堆外內(nèi)存打交道,但這并不代表你從未接觸過堆外內(nèi)存,像大家經(jīng)常使用的諸如:RocketMQ、MapDB 等中間件產(chǎn)品底層實現(xiàn)都是基于堆外存儲的,換句話說,我們幾乎每天都在間接與堆外內(nèi)存打交道。那么究竟為什么需要使用到堆外內(nèi)存呢?簡單來說,主要是出于以下 3 個方面的考慮:
- 減少 GC 次數(shù)和降低 Stop-the-world 時間;
- 可以擴展和使用更大的內(nèi)存空間;
- 可以省去物理內(nèi)存和堆內(nèi)存之間的數(shù)據(jù)復(fù)制步驟。
在 Java14 之前,如果開發(fā)人員想要操作堆外內(nèi)存,通常的做法就是使用 ByteBuffer 或者 Unsafe,甚至是 JNI 等方式,但無論使用哪一種方式,均無法同時有效解決安全性和高效性等 2 個問題,并且,堆外內(nèi)存的釋放也是一個令人頭痛的問題。以 DirectByteBuffer 為例,該對象僅僅只是一個引用,其背后還關(guān)聯(lián)著一大段堆外內(nèi)存,由于 DirectByteBuffer 對象實例仍然是存儲在堆空間內(nèi),只有當 DirectByteBuffer 對象被 GC 回收時,其背后的堆外內(nèi)存才會被進一步釋放。
在此大家需要注意,程序中通過 ByteBuffer.allocateDirect()方法來申請物理內(nèi)存資源所耗費的成本遠遠高于直接在 on-heap 中的操作,而且實際開發(fā)過程中還需要考慮數(shù)據(jù)結(jié)構(gòu)如何設(shè)計、序列化/反序列化如何支撐等諸多難題,所以與其使用語法層面的 API 倒不如直接使用 MapDB 等開源產(chǎn)品來得更實惠。
如今,在堆外內(nèi)存領(lǐng)域,我們似乎又多了一個選擇,從 Java14 開始,Java 的設(shè)計者們在語法層面為大家?guī)砹藣湫碌?Memory Access API,極大程度上簡化了開發(fā)難度,并得以有效的解決了安全性和高效性等 2 個核心問題。示例:
// 獲取內(nèi)存訪問var句柄 var handle = MemoryHandles.varHandle(char.class,ByteOrder.nativeOrder()); // 申請200字節(jié)的堆外內(nèi)存 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));} }關(guān)于堆外內(nèi)存段的釋放,Memory Access API 提供有顯式和隱式 2 種方式,開發(fā)人員除了可以在程序中通過 MemorySegment 的 close()方法來顯式釋放所申請的內(nèi)存資源外,還可以注冊 Cleaner 清理器來實現(xiàn)資源的隱式釋放,后者會在 GC 確定目標內(nèi)存段不再可訪問時,釋放與之關(guān)聯(lián)的堆外內(nèi)存資源。
7、結(jié)束語
Java 在更新版本周期為每半年發(fā)布一次之后,目前來看,確實是嚴格保持每半年更新的節(jié)奏。Java 14 版本的發(fā)布帶來了不少新特性、功能實用性的增強、性能提升和 GC 方面的改進嘗試。本文僅針對其中對使用人員影響較大的以及其中主要的特性做了介紹,如有興趣,您還可以自行下載相關(guān)代碼,繼續(xù)深入研究。
更多內(nèi)容:
更多內(nèi)容大家可以關(guān)注一下個人博客網(wǎng),https://blog.xueqimiao.com/,內(nèi)容更豐富喔。
總結(jié)
以上是生活随笔為你收集整理的七、Java 14 新特性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《2013-I want to talk
- 下一篇: java美元兑换,(Java实现) 美元