天天看點

java 新特性_Java 13 新特性概述

Java 13 新特性概述

Java 13 已如期于 2019年9 月 17 日正式釋出,此次更新是繼半年前 Java 12 這大版本釋出之後的一次正常版本更新,在這一版中,主要帶來了 ZGC 增強、更新 Socket 實作、Switch 表達式更新等方面的改動、增強。

本文主要針對 Java 13 中主要的新特性展開介紹,帶你快速了解 Java 13 帶來的不同體驗。

動态應用程式類-資料共享

在 Java 10 中,為了改善應用啟動時間和記憶體空間占用,通過使用 APP CDS,加大了 CDS 的使用範圍,允許自定義的類加載器也可以加載自定義類給多個 JVM 共享使用,具體介紹可以參考 Java 10 新特性介紹 一文詳細介紹,在此就不再繼續展開 。

Java 13 中對 Java 10 中引入的 應用程式類資料共享進行了進一步的簡化、改進和擴充,即:允許在 Java 應用程式執行結束時動态進行類歸檔,具體能夠被歸檔的類包括:所有已被加載,但不屬于預設基層 CDS 的應用程式類和引用類庫中的類。通過這種改進,可以提高應用程式類-資料使用上的簡易性,減少在使用類-資料存檔中需要為應用程式建立類加載清單的必要,簡化使用類-資料共享的步驟,以便更簡單、便捷地使用 CDS 存檔。

在 Java 中,如果要執行一個類,首先需要将類編譯成對應的位元組碼檔案,以下是 JVM 裝載、執行等需要的一系列準備步驟:假設給定一個類名,JVM 将在磁盤上查找到該類對應的位元組碼檔案,并将其進行加載,驗證位元組碼檔案,準備,解析,初始化,根據其内部資料結構加載到記憶體中。當然,這一連串的操作都需要一些時間,這在 JVM 啟動并且需要加載至少幾百個甚至是數千個類時,加載時間就尤其明顯。

Java 10 中的 App CDS 主要是為了将不變的類資料,進行一次建立,然後存儲到歸檔中,以便在應用重新開機之後可以對其進行記憶體映射而直接使用,同時也可以在運作的 JVM 執行個體之間共享使用。但是在 Java 10 中使用 App CDS 需要進行如下操作:

建立需要進行類歸檔的類清單

建立歸檔

使用歸檔方式啟動

在使用歸檔檔案啟動時,JVM 将歸檔檔案映射到其對應的記憶體中,其中包含所需的大多數類,而

需要使用多麼複雜的類加載機制。甚至可以在并發運作的 JVM 執行個體之間共享記憶體區域,通過這種方式可以釋放需要在每個 JVM 執行個體中建立相同資訊時浪費的記憶體,進而節省了記憶體空間。

在 Java 12 中,預設開啟了對 JDK 自帶 JAR 包類的存檔,如果想關閉對自帶類庫的存檔,可以在啟動參數中加上:

而在 Java 13 中,可以不用提供歸檔類清單,而是通過更簡潔的方式來建立包含應用程式類的歸檔。具體可以使用參數 -XX:ArchiveClassesAtExit 來控制應用程式在退出時生成存檔,也可以使用 -XX:SharedArchiveFile 來使用動态存檔功能,詳細使用見如下示例。

清單 1. 建立存檔檔案示例

清單 2. 使用存檔檔案示例

上述就是在 Java 應用程式執行結束時動态進行類歸檔,并且在 Java 10 的基礎上,将多條指令進行了簡化,可以更加友善地使用類歸檔功能。

增強 ZGC 釋放未使用記憶體

ZGC 是 Java 11 中引入的最為矚目的垃圾回收特性,是一種可伸縮、低延遲的垃圾收集器,不過在 Java 11 中是實驗性的引入,主要用來改善 GC 停頓時間,并支援幾百 MB 至幾個 TB 級别大小的堆,并且應用吞吐能力下降不會超過 15%,目前隻支援 Linux/x64 位平台的這樣一種新型垃圾收集器。

通過在實際中的使用,發現 ZGC 收集器中并沒有像 Hotspot 中的 G1 和 Shenandoah 垃圾收集器一樣,能夠主動将未使用的記憶體釋放給作業系統的功能。對于大多數應用程式來說,CPU 和記憶體都屬于有限的緊缺資源,特别是現在使用的雲上或者虛拟化環境中。如果應用程式中的記憶體長期處于空閑狀态,并且還不能釋放給作業系統,這樣會導緻其他需要記憶體的應用無法配置設定到需要的記憶體,而這邊應用配置設定的記憶體還處于空閑狀态,處于"忙的太忙,閑的太閑"的非公平狀态,并且也容易導緻基于虛拟化的環境中,因為這些實際并未使用的資源而多付費的情況。由此可見,将未使用記憶體釋放給系統主記憶體是一項非常有用且亟需的功能。

ZGC 堆由一組稱為 ZPages 的堆區域組成。在 GC 周期中清空 ZPages 區域時,它們将被釋放并傳回到頁面緩存 ZPageCache 中,此緩存中的 ZPages 按最近最少使用(LRU)的順序,并按照大小進行組織。在 Java 13 中,ZGC 将向作業系統傳回被辨別為長時間未使用的頁面,這樣它們将可以被其他程序重用。同時釋放這些未使用的記憶體給作業系統不會導緻堆大小縮小到參數設定的最小大小以下,如果将最小和最大堆大小設定為相同的值,則不會釋放任何記憶體給作業系統。

Java 13 中對 ZGC 的改進,主要展現在下面幾點:

釋放未使用記憶體給作業系統

支援最大堆大小為 16TB

添加參數:-XX:SoftMaxHeapSize 來軟限制堆大小

這裡提到的是軟限制堆大小,是指 GC 應努力是堆大小不要超過指定大小,但是如果實際需要,也還是允許 GC 将堆大小增加到超過 SoftMaxHeapSize 指定值。主要用在下面幾種情況:當希望降低堆占用,同時保持應對堆空間臨時增加的能力,亦或想保留充足記憶體空間,以能夠應對記憶體配置設定,而不會因為記憶體配置設定意外增加而陷入配置設定停滞狀态。不應将 SoftMaxHeapSize 設定為大于最大堆大小(-Xmx 的值,如果未在指令行上設定,則此标志應預設為最大堆大小。

Java 13 中,ZGC 記憶體釋放功能,預設情況下是開啟的,不過可以使用參數:-XX:-ZUncommit 顯式關閉,同時如果将最小堆大小 (-Xms) 配置為等于最大堆大小 (-Xmx),則将隐式禁用此功能。

還可以使用參數:-XX:ZUncommitDelay = (預設值為 300 秒)來配置延遲釋放,此延遲時間可以指定釋放多長時間之前未使用的記憶體。

Socket API 重構

Java 中的 Socket API 已經存在了二十多年了,盡管這麼多年來,一直在維護和更新中,但是在實際使用中遇到一些局限性,并且不容易維護和調試,是以要對其進行大修大改,才能跟得上現代技術的發展,畢竟二十多年來,技術都發生了深刻的變化。Java 13 為 Socket API 帶來了新的底層實作方法,并且在 Java 13 中是預設使用新的 Socket 實作,使其易于發現并在排除問題同時增加可維護性。

Java Socket API(java.net.ServerSocket 和 java.net.Socket)包含允許監聽控制伺服器和發送資料的套接字對象。可以使用 ServerSocket 來監聽連接配接請求的端口,一旦連接配接成功就傳回一個 Socket 對象,可以使用該對象讀取發送的資料和進行資料寫回操作,而這些類的繁重工作都是依賴于 SocketImpl 的内部實作,伺服器的發送和接收兩端都基于 SOCKS 進行實作的。

在 Java 13 之前,通過使用 PlainSocketImpl 作為 SocketImpl 的具體實作。

Java 13 中的新底層實作,引入 NioSocketImpl 的實作用以替換 SocketImpl 的 PlainSocketImpl 實作,此實作與 NIO(新 I/O)實作共享相同的内部基礎結構,并且與現有的緩沖區高速緩存機制內建在一起,是以不需要使用線程堆棧。除了這些更改之外,還有其他一些更便利的更改,如使用 java.lang.ref.Cleaner 機制來關閉套接字(如果 SocketImpl 實作在尚未關閉的套接字上被進行了垃圾收集),以及在輪詢時套接字處于非阻塞模式時處理逾時操作等方面。

為了最小化在重新實作已使用二十多年的方法時出現問題的風險,在引入新實作方法的同時,之前版本的實作還未被移除,可以通過使用下列系統屬性以重新使用原實作方法:

另外需要注意的是,SocketImpl 是一種傳統的 SPI 機制,同時也是一個抽象類,并未指定具體的實作,是以,新的實作方式嘗試模拟未指定的行為,以達到與原有實作相容的目的。但是,在使用新實作時,有些基本情況可能會失敗,使用上述系統屬性可以糾正遇到的問題,下面兩個除外。

老版本中,PlainSocketImpl 中的 getInputStream()和 getOutputStream()方法傳回的 InputStream 和 OutputStream 分别來自于其對應的擴充類型 FileInputStream 和 FileOutputStream,而這個在新版實作中則沒有。

使用自定義或其它平台的 SocketImpl 的伺服器套接字無法接受使用其他(自定義或其它平台)類型 SocketImpl 傳回 Sockets 的連接配接。

通過這些更改,Java Socket API 将更易于維護,更好地維護将使套接字代碼的可靠性得到改善。同時 NIO 實作也可以在基礎層面完成,進而保持 Socket 和 ServerSocket 類層面上的不變。

Switch 表達式擴充(預覽功能)

在 Java 12 中引入了 Switch 表達式作為預覽特性,而在 Java 13 中對 Switch 表達式做了增強改進,在塊中引入了 yield 語句來傳回值,而不是使用 break。這意味着,Switch 表達式(傳回值)應該使用 yield,而 Switch 語句(不傳回值)應該使用 break,而在此之前,想要在 Switch 中傳回内容,還是比較麻煩的,隻不過目前還處于預覽狀态。

在 Java 13 之後,Switch 表達式中就多了一個關鍵字用于跳出 Switch 塊的關鍵字 yield,主要用于傳回一個值,它和 return 的差別在于:return 會直接跳出目前循環或者方法,而 yield 隻會跳出目前 Switch 塊,同時在使用 yield 時,需要有 default 條件。

在 Java 12 之前,傳統 Switch 語句寫法為:

清單 3. 傳統形式

在 Java 12 之後,關于 Switch 表達式的寫法改進為如下:

清單 4. 标簽簡化形式

而在 Java 13 中,,value break 語句不再被編譯,而是用 yield 來進行值傳回,上述寫法被改為如下寫法:

清單 5. yield 傳回值形式

文本塊(預覽功能)

一直以來,Java 語言在定義字元串的方式是有限的,字元串需要以雙引号開頭,以雙引号結尾,這導緻字元串不能夠多行使用,而是需要通過換行轉義或者換行連接配接符等方式來變通支援多行,但這樣會增加編輯工作量,同時也會導緻所在代碼段難以閱讀、難以維護。

Java 13 引入了文本塊來解決多行文本的問題,文本塊以三重雙引号開頭,并以同樣的以三重雙引号結尾終止,它們之間的任何内容都被解釋為字元串的一部分,包括換行符,避免了對大多數轉義序列的需要,并且它仍然是普通的 java.lang.String 對象,文本塊可以在 Java 中可以使用字元串文字的任何地方使用,而與編譯後的代碼沒有差別,還增強了 Java 程式中的字元串可讀性。并且通過這種方式,可以更直覺地表示字元串,可以支援跨越多行,而且不會出現轉義的視覺混亂,将可以廣泛提高 Java 類程式的可讀性和可寫性。

在 Java 13 之前,多行字元串寫法為:

清單 6. 多行字元串寫法

在 Java 13 引入文本塊之後,寫法為:

清單 7. 多行文本塊寫法

文本塊是作為預覽功能引入到 Java 13 中的,這意味着它們不包含在相關的 Java 語言規範中,這樣做的好處是友善使用者測試功能并提供回報,後續更新可以根據回報來改進功能,或者必要時甚至删除該功能,如果該功能立即成為 Java SE

準的一部分,則進行更改将變得更加困難。重要的是要意識到預覽功能不是 beta 形式。

由于預覽功能不是規範的一部分,是以有必要為編譯和運作時明确啟用它們。需要使用下面兩個指令行參數來啟用預覽功能:

清單 8. 啟用預覽功能

結束語

Java 在更新釋出周期為每半年釋出一次之後,在合并關鍵特性、快速得到開發者回報等方面,做得越來越好。從 Java 11 到 Java 13,目前确實是嚴格保持半年更新的節奏。Java 13 版本的釋出帶來了些新特性和功能增強、性能提升和改進嘗試,不過 Java 13 不是 LTS 版本,本文針對其中對使用人員影響重大的以及主要的特性做了介紹,如有興趣,您可以自行下載下傳相關代碼,繼續深入研究。

參考資源