天天看點

四、Java 11 新特性四、Java 11 新特性

四、Java 11 新特性

Java 11 在 2018 年 9 月 25 日正式釋出,之前在 Java 10 新特性介紹 中介紹過,為了加快的版本疊代、跟進社群回報,Java 的版本釋出周期調整為每六個月一次——即每半年釋出一個大版本,每個季度釋出一個中間特性版本,并且做出不會跳票的承諾。通過這樣的方式,Java 開發團隊能夠将一些重要特性盡早的合并到 Java Release 版本中,以便快速得到開發者的回報,避免出現類似 Java 9 釋出時的兩次延期的情況。

按照官方介紹,新的版本釋出周期将會嚴格按照時間節點,于每年的 3 月和 9 月釋出,Java 11 釋出的時間節點也正好處于 Java 8 免費更新到期的前夕。與 Java 9 和 Java 10 這兩個被稱為”功能性的版本”不同,Java 11 僅将提供長期支援服務(LTS, Long-Term-Support),還将作為 Java 平台的預設支援版本,并且會提供技術支援直至 2023 年 9 月,對應的更新檔和安全警告等支援将持續至 2026 年。

本文主要針對 Java 11 中的新特性展開介紹,讓您快速了解 Java 11 帶來的變化。

1、知識體系

四、Java 11 新特性四、Java 11 新特性

2、基于嵌套的通路控制

與 Java 語言中現有的嵌套類型概念一緻, 嵌套通路控制是一種控制上下文通路的政策,允許邏輯上屬于同一代碼實體,但被編譯之後分為多個分散的 class 檔案的類,無需編譯器額外的建立可擴充的橋接通路方法,即可通路彼此的私有成員,并且這種改進是在 Java 位元組碼級别的。

在 Java 11 之前的版本中,編譯之後的 class 檔案中通過 InnerClasses 和 Enclosing Method 兩種屬性來幫助編譯器确認源碼的嵌套關系,每一個嵌套的類會編譯到自己所在的 class 檔案中,不同類的檔案通過上面介紹的兩種屬性的來互相連接配接。這兩種屬性對于編譯器确定互相之間的嵌套關系已經足夠了,但是并不适用于通路控制。這裡大家可以寫一段包含内部類的代碼,并将其編譯成 class 檔案,然後通過 javap 指令行來分析,礙于篇幅,這裡就不展開讨論了。

Java 11 中引入了兩個新的屬性:一個叫做 NestMembers 的屬性,用于辨別其它已知的靜态 nest 成員;另外一個是每個 nest 成員都包含的 NestHost 屬性,用于辨別出它的 nest 宿主類。

3、标準 HTTP Client 更新

Java 11 對 Java 9 中引入并在 Java 10 中進行了更新的 Http Client API 進行了标準化,在前兩個版本中進行孵化的同時,Http Client 幾乎被完全重寫,并且現在完全支援異步非阻塞。

新版 Java 中,Http Client 的包名由 jdk.incubator.http 改為 java.net.http,該 API 通過 CompleteableFutures 提供非阻塞請求和響應語義,可以聯合使用以觸發相應的動作,并且 RX Flo w 的概念也在 Java 11 中得到了實作。現在,在使用者層請求釋出者和響應釋出者與底層套接字之間追蹤資料流更容易了。這降低了複雜性,并最大程度上提高了 HTTP/1 和 HTTP/2 之間的重用的可能性。

Java 11 中的新 Http Client API,提供了對 HTTP/2 等業界前沿标準的支援,同時也向下相容 HTTP/1.1,精簡而又友好的 API 接口,與主流開源 API(如:Apache HttpClient、Jetty、OkHttp 等)類似甚至擁有更高的性能。與此同時它是 Java 在 Reactive-Stream 方面的第一個生産實踐,其中廣泛使用了 Java Flow API,終于讓 Java 标準 HTTP 類庫在擴充能力等方面,滿足了現代網際網路的需求,是一個難得的現代 Http/2 Client API 标準的實作,Java 工程師終于可以擺脫老舊的 HttpURLConnection 了。下面模拟 Http GET 請求并列印傳回内容:

清單 1. GET 請求示例

HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create("http://openjdk.java.net/"))
          .build();
    client.sendAsync(request, BodyHandlers.ofString())
          .thenApply(HttpResponse::body)
          .thenAccept(System.out::println)
          .join();
           

4、Epsilon:低開銷垃圾回收器

Epsilon 垃圾回收器的目标是開發一個控制記憶體配置設定,但是不執行任何實際的垃圾回收工作。它提供一個完全消極的 GC 實作,配置設定有限的記憶體資源,最大限度的降低記憶體占用和記憶體吞吐延遲時間。

Java 版本中已經包含了一系列的高度可配置化的 GC 實作。各種不同的垃圾回收器可以面對各種情況。但是有些時候使用一種獨特的實作,而不是将其堆積在其他 GC 實作上将會是事情變得更加簡單。

下面是 no-op GC 的幾個使用場景:

  • 性能測試:什麼都不執行的 GC 非常适合用于 GC 的差異性分析。no-op (無操作)GC 可以用于過濾掉 GC 誘發的性能損耗,比如 GC 線程的排程,GC 屏障的消耗,GC 周期的不合适觸發,記憶體位置變化等。此外有些延遲者不是由于 GC 引起的,比如 scheduling hiccups, compiler transition hiccups,是以去除 GC 引發的延遲有助于統計這些延遲。
  • 記憶體壓力測試:在測試 Java 代碼時,确定配置設定記憶體的門檻值有助于設定記憶體壓力常量值。這時 no-op 就很有用,它可以簡單地接受一個配置設定的記憶體配置設定上限,當記憶體超限時就失敗。例如:測試需要配置設定小于 1G 的記憶體,就使用-Xmx1g 參數來配置 no-op GC,然後當記憶體耗盡的時候就直接 crash。
  • VM 接口測試:以 VM 開發視角,有一個簡單的 GC 實作,有助于了解 VM-GC 的最小接口實作。它也用于證明 VM-GC 接口的健全性。
  • 極度短暫 job 任務:一個短聲明周期的 job 任務可能會依賴快速退出來釋放資源,這個時候接收 GC 周期來清理 heap 其實是在浪費時間,因為 heap 會在退出時清理。并且 GC 周期可能會占用一會時間,因為它依賴 heap 上的資料量。 延遲改進:對那些極端延遲敏感的應用,開發者十厘清楚記憶體占用,或者是幾乎沒有垃圾回收的應用,此時耗時較長的 GC 周期将會是一件壞事。
  • 吞吐改進:即便對那些無需記憶體配置設定的工作,選擇一個 GC 意味着選擇了一系列的 GC 屏障,所有的 OpenJDK GC 都是分代的,是以他們至少會有一個寫屏障。避免這些屏障可以帶來一點點的吞吐量提升。

Epsilon 垃圾回收器和其他 OpenJDK 的垃圾回收器一樣,可以通過參數

-XX:+UseEpsilonGC

開啟。

Epsilon 線性配置設定單個連續記憶體塊。可複用現存 VM 代碼中的 TLAB 部分的配置設定功能。非 TLAB 配置設定也是同一段代碼,因為在此方案中,配置設定 TLAB 和配置設定大對象隻有一點點的不同。Epsilon 用到的 barrier 是空的(或者說是無操作的)。因為該 GC

執行任何的 GC 周期,不用關系對象圖,對象标記,對象複制等。引進一種新的 barrier-set 實作可能是該 GC 對 JVM 最大的變化。

5、簡化啟動單個源代碼檔案的方法

Java 11 版本中最令人興奮的功能之一是增強 Java 啟動器,使之能夠運作單一檔案的 Java 源代碼。此功能允許使用 Java 解釋器直接執行 Java 源代碼。源代碼在記憶體中編譯,然後由解釋器執行。唯一的限制在于所有相關的類必須定義在同一個 Java 檔案中。

此功能對于開始學習 Java 并希望嘗試簡單程式的人特别有用,并且能與 jshell 一起使用,将成為任何初學者學習語言的一個很好的工具集。不僅初學者會受益,專業人員還可以利用這些工具來探索新的語言更改或嘗試未知的 API。

如今單檔案程式在編寫小實用程式時很常見,特别是腳本語言領域。從中開發者可以省去用 Java 編譯程式等不必要工作,以及減少新手的入門障礙。在基于 Java 10 的程式實作中可以通過三種方式啟動:

  • 作為 * .class 檔案
  • 作為 * .jar 檔案中的主類
  • 作為子產品中的主類

而在最新的 Java 11 中新增了一個啟動方式,即可以在源代碼中聲明類,例如:如果名為 HelloWorld.java 的檔案包含一個名為 hello.World 的類,那麼該指令:

$ java HelloWorld.java
           

也等同于:

$ javac HelloWorld.java
$ java -cp . hello.World 
           

6、用于 Lambda 參數的局部變量文法

在 Lambda 表達式中使用局部變量類型推斷是 Java 11 引入的唯一與語言相關的特性,這一節,我們将探索這一新特性。

從 Java 10 開始,便引入了局部變量類型推斷這一關鍵特性。類型推斷允許使用關鍵字 var 作為局部變量的類型而不是實際類型,編譯器根據配置設定給變量的值推斷出類型。這一改進簡化了代碼編寫、節省了開發者的工作時間,因為不再需要顯式聲明局部變量的類型,而是可以使用關鍵字 var,且不會使源代碼過于複雜。

可以使用關鍵字 var 聲明局部變量,如下所示:

var s = "Hello Java 11";
System.out.println(s); 
           

但是在 Java 10 中,還有下面幾個限制:

  • 隻能用于局部變量上
  • 聲明時必須初始化
  • 不能用作方法參數
  • 不能在 Lambda 表達式中使用

Java 11 與 Java 10 的不同之處在于允許開發者在 Lambda 表達式中使用 var 進行參數聲明。乍一看,這一舉措似乎有點多餘,因為在寫代碼過程中可以省略 Lambda 參數的類型,并通過類型推斷确定它們。但是,添加上類型定義同時使用 @Nonnull 和 @Nullable 等類型注釋還是很有用的,既能保持與局部變量的一緻寫法,也不丢失代碼簡潔。

Lambda 表達式使用隐式類型定義,它形參的所有類型全部靠推斷出來的。隐式類型 Lambda 表達式如下:

Java 10 為局部變量提供隐式定義寫法如下:

var x = new Foo();
for (var x : xs) { ... }
try (var x = ...) { ... } catch ... 
           

為了 Lambda 類型表達式中正式參數定義的文法與局部變量定義文法的不一緻,且為了保持與其他局部變量用法上的一緻性,希望能夠使用關鍵字 var 隐式定義 Lambda 表達式的形參:

于是在 Java 11 中将局部變量和 Lambda 表達式的用法進行了統一,并且可以将注釋應用于局部變量和 Lambda 表達式:

@Nonnull var x = new Foo();
(@Nonnull var x, @Nullable var y) -> x.process(y) 
           

7、低開銷的 Heap Profiling

Java 11 中提供一種低開銷的 Java 堆配置設定采樣方法,能夠得到堆配置設定的 Java 對象資訊,并且能夠通過 JVMTI 通路堆資訊。

引入這個低開銷記憶體分析工具是為了達到如下目的:

  • 足夠低的開銷,可以預設且一直開啟
  • 能通過定義好的程式接口通路
  • 能夠對所有堆配置設定區域進行采樣
  • 能給出正在和未被使用的 Java 對象資訊

對使用者來說,了解它們堆裡的記憶體分布是非常重要的,特别是遇到生産環境中出現的高 CPU、高記憶體占用率的情況。目前有一些已經開源的工具,允許使用者分析應用程式中的堆使用情況,比如:Java Flight Recorder、jmap、YourKit 以及 VisualVM tools.。但是這些工具都有一個明顯的不足之處:無法得到對象的配置設定位置,headp dump 以及 heap histogram 中都沒有包含對象配置設定的具體資訊,但是這些資訊對于調試記憶體問題至關重要,因為它能夠告訴開發人員他們的代碼中發生的高記憶體配置設定的确切位置,并根據實際源碼來分析具體問題,這也是 Java 11 中引入這種低開銷堆配置設定采樣方法的原因。

8、支援 TLS 1.3 協定

Java 11 中包含了傳輸層安全性(TLS)1.3 規範(RFC 8446)的實作,替換了之前版本中包含的 TLS,包括 TLS 1.2,同時還改進了其他 TLS 功能,例如 OCSP 裝訂擴充(RFC 6066,RFC 6961),以及會話散列和擴充主密鑰擴充(RFC 7627),在安全性和性能方面也做了很多提升。

新版本中包含了 Java 安全套接字擴充(JSSE)提供 SSL,TLS 和 DTLS 協定的架構和 Java 實作。目前,JSSE API 和 JDK 實作支援 SSL 3.0,TLS 1.0,TLS 1.1,TLS 1.2,DTLS 1.0 和 DTLS 1.2。

同時 Java 11 版本中實作的 TLS 1.3,重新定義了以下新标準算法名稱:

  • TLS 協定版本名稱:TLSv1.3
  • SSLContext 算法名稱:TLSv1.3
  • TLS 1.3 的 TLS 密碼套件名稱:TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384
  • 用于 X509KeyManager 的 keyType:RSASSA-PSS
  • 用于 X509TrustManager 的 authType:RSASSA-PSS

還為 TLS 1.3 添加了一個新的安全屬性 jdk.tls.keyLimits。當處理了特定算法的指定資料量時,觸發握手後,密鑰和 IV 更新以導出新密鑰。還添加了一個新的系統屬性 jdk.tls.server.protocols,用于在 SunJSSE 提供程式的伺服器端配置預設啟用的協定套件。

之前版本中使用的 KRB5 密碼套件實作已從 Java 11 中删除,因為該算法已不再安全。同時注意,TLS 1.3 與以前的版本不直接相容。

更新到 TLS 1.3 之前,需要考慮如下幾個相容性問題:

  • TLS 1.3 使用半關閉政策,而 TLS 1.2 以及之前版本使用雙工關閉政策,對于依賴于雙工關閉政策的應用程式,更新到 TLS 1.3 時可能存在相容性問題。
  • TLS 1.3 使用預定義的簽名算法進行證書身份驗證,但實際場景中應用程式可能會使用不被支援的簽名算法。
  • TLS 1.3 再支援 DSA 簽名算法,如果在伺服器端配置為僅使用 DSA 證書,則無法更新到 TLS 1.3。
  • TLS 1.3 支援的加密套件與 TLS 1.2 和早期版本不同,若應用程式寫死了加密算法單元,則在更新的過程中需要修改相應代碼才能更新使用 TLS 1.3。
  • TLS 1.3 版本的 session 用行為及秘鑰更新行為與 1.2 及之前的版本不同,若應用依賴于 TLS 協定的握手過程細節,則需要注意。

9、ZGC:可伸縮低延遲垃圾收集器

ZGC 即 Z Garbage Collector(垃圾收集器或垃圾回收器),這應該是 Java 11 中最為矚目的特性,沒有之一。ZGC 是一個可伸縮的、低延遲的垃圾收集器,主要為了滿足如下目标進行設計:

  • GC 停頓時間不超過 10ms
  • 即能處理幾百 MB 的小堆,也能處理幾個 TB 的大堆
  • 應用吞吐能力不會下降超過 15%(與 G1 回收算法相比)
  • 友善在此基礎上引入新的 GC 特性和利用 colord
  • 針以及 Load barriers 優化奠定基礎
  • 目前隻支援 Linux/x64 位平台 停頓時間在 10ms 以下,10ms 其實是一個很保守的資料,即便是 10ms 這個資料,也是 GC 調優幾乎達不到的極值。根據 SPECjbb 2015 的基準測試,128G 的大堆下最大停頓時間才 1.68ms,遠低于 10ms,和 G1 算法相比,改進非常明顯。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-6jk1VX5O-1672103952108)(images/4.JDK11新特性/java-11-1.png)]

本圖檔引用自: The Z Garbage Collector – An Introduction

不過目前 ZGC 還處于實驗階段,目前隻在 Linux/x64 上可用,如果有足夠的需求,将來可能會增加對其他平台的支援。同時作為實驗性功能的 ZGC 将不會出現在 JDK 建構中,除非在編譯時使用 configure 參數:

--with-jvm-features=zgc

顯式啟用。

在實驗階段,編譯完成之後,已經迫不及待的想試試 ZGC,需要配置以下 JVM 參數,才能使用 ZGC,具體啟動 ZGC 參數如下:

其中參數: -Xmx 是 ZGC 收集器中最重要的調優選項,大大解決了程式員在 JVM 參數調優上的困擾。ZGC 是一個并發收集器,必須要設定一個最大堆的大小,應用需要多大的堆,主要有下面幾個考量:

  • 對象的配置設定速率,要保證在 GC 的時候,堆中有足夠的記憶體配置設定新對象。
  • 一般來說,給 ZGC 的記憶體越多越好,但是也不能浪費記憶體,是以要找到一個平衡。

10、飛行記錄器

飛行記錄器之前是商業版 JDK 的一項分析工具,但在 Java 11 中,其代碼被包含到公開代碼庫中,這樣所有人都能使用該功能了。

Java 語言中的飛行記錄器類似飛機上的黑盒子,是一種低開銷的事件資訊收集架構,主要用于對應用程式和 JVM 進行故障檢查、分析。飛行記錄器記錄的主要資料源于應用程式、JVM 和 OS,這些事件資訊儲存在單獨的事件記錄檔案中,故障發生後,能夠從事件記錄檔案中提取出有用資訊對故障進行分析。

啟用飛行記錄器參數如下:

也可以使用 bin/jcmd 工具啟動和配置飛行記錄器:

清單 2. 飛行記錄器啟動、配置參數示例

$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop
           

JFR 使用測試:

清單 3. JFR 使用示例

public class FlightRecorderTest extends Event {
        @Label("Hello World")
        @Description("Helps the programmer getting started")
        static class HelloWorld extends Event {
            @Label("Message")
            String message;
        }

        public static void main(String[] args) {
            HelloWorld event = new HelloWorld();
            event.message = "hello, world!";
            event.commit();
        }
    }
           

在運作時加上如下參數:

java -XX:StartFlightRecording=duration=1s, filename=recording.jfr 
           

下面讀取上一步中生成的 JFR 檔案:recording.jfr

清單 4. 飛行記錄器分析示例

public void readRecordFile() throws IOException {
        final Path path = Paths.get("D:\\ java \\recording.jfr");
        final List<RecordedEvent> recordedEvents = RecordingFile.readAllEvents(path);
        for (RecordedEvent event : recordedEvents) {
            System.out.println(event.getStartTime() + "," + event.getValue("message"));
        }
    }  
           

11、動态類檔案常量

為了使 JVM 對動态語言更具吸引力,Java 的第七個版本已将 invokedynamic 引入其指令集。

不過 Java 開發人員通常不會注意到此功能,因為它隐藏在 Java 位元組代碼中。通過使用 invokedynamic,可以延遲方法調用的綁定,直到第一次調用。例如,Java 語言使用該技術來實作 Lambda 表達式,這些表達式僅在首次使用時才顯示出來。這樣做,invokedynamic 已經演變成一種必不可少的語言功能。

Java 11 引入了類似的機制,擴充了 Java 檔案格式,以支援新的常量池:CONSTANT_Dynamic,它在初始化的時候,像 invokedynamic 指令生成代理方法一樣,委托給 bootstrap 方法進行初始化建立,對上層軟體沒有很大的影響,降低開發新形式的可實作類檔案限制帶來的成本和幹擾。

12、結束語

Java 在更新釋出周期為每半年釋出一次之後,在合并關鍵特性、快速得到開發者回報等方面,做得越來越好。Java 11 版本的釋出也帶來了不少新特性和功能增強、性能提升、基礎能力的全面進步和突破,本文針對其中對使用人員影響重大的以及主要的特性做了介紹。Java 12 即将到來,您準備好了嗎?

本文僅代表作者個人觀點,不代表其所在機關的意見,如有不足之處,還望您能夠海涵。希望您能夠回報意見,交流心得,一同進步。

更多内容:

更多内容大家可以關注一下個人部落格網,https://blog.xueqimiao.com/,内容更豐富喔。