Java 11 新特性介紹

Java 11 已於 2018 年 9 月 25 日正式發佈,以前在 Java 10 新特性介紹中介紹過,爲了加快的版本迭代、跟進社區反饋,Java 的版本發佈週期調整爲每六個月一次——即每半年發佈一個大版本,每一個季度發佈一箇中間特性版本,而且作出不會跳票的承諾。經過這樣的方式,Java 開發團隊可以將一些重要特性儘早的合併到 Java Release 版本中,以便快速獲得開發者的反饋,避免出現相似 Java 9 發佈時的兩次延期的狀況。html

按照官方介紹,新的版本發佈週期將會嚴格按照時間節點,於每一年的 3 月和 9 月發佈,Java 11 發佈的時間節點也正好處於 Java 8 免費更新到期的前夕。與 Java 9 和 Java 10 這兩個被稱爲"功能性的版本"不一樣,Java 11 僅將提供長期支持服務(LTS, Long-Term-Support),還將做爲 Java 平臺的默認支持版本,而且會提供技術支持直至 2023 年 9 月,對應的補丁和安全警告等支持將持續至 2026 年。java

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

基於嵌套的訪問控制

與 Java 語言中現有的嵌套類型概念一致, 嵌套訪問控制是一種控制上下文訪問的策略,容許邏輯上屬於同一代碼實體,但被編譯以後分爲多個分散的 class 文件的類,無需編譯器額外的建立可擴展的橋接訪問方法,便可訪問彼此的私有成員,而且這種改進是在 Java 字節碼級別的。算法

在 Java 11 以前的版本中,編譯以後的 class 文件中經過 InnerClasses 和 Enclosing Method 兩種屬性來幫助編譯器確認源碼的嵌套關係,每個嵌套的類會編譯到本身所在的 class 文件中,不一樣類的文件經過上面介紹的兩種屬性的來相互鏈接。這兩種屬性對於編譯器肯定相互之間的嵌套關係已經足夠了,可是並不適用於訪問控制。這裏你們能夠寫一段包含內部類的代碼,並將其編譯成 class 文件,而後經過 javap 命令行來分析,礙於篇幅,這裏就不展開討論了。shell

Java 11 中引入了兩個新的屬性:一個叫作 NestMembers 的屬性,用於標識其它已知的靜態 nest 成員;另一個是每一個 nest 成員都包含的 NestHost 屬性,用於標識出它的 nest 宿主類。bootstrap

標準 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的概念也在 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 請求並打印返回內容:session

清單 1. GET 請求示例
1
2
3
4
5
6
7
8
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();

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 最大的變化。

簡化啓動單個源代碼文件的方法

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

用於 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 表達式以下:

(x, y) -> x.process(y)

Java 10 爲局部變量提供隱式定義寫法以下:

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

爲了 Lambda 類型表達式中正式參數定義的語法與局部變量定義語法的不一致,且爲了保持與其餘局部變量用法上的一致性,但願可以使用關鍵字 var 隱式定義 Lambda 表達式的形參:

(var x, var y) -> x.process(y)

因而在 Java 11 中將局部變量和 Lambda 表達式的用法進行了統一,而且能夠將註釋應用於局部變量和 Lambda 表達式:

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

低開銷的 Heap Profiling

Java 11 中提供一種低開銷的 Java 堆分配採樣方法,可以獲得堆分配的 Java 對象信息,而且可以經過 JVMTI 訪問堆信息。

引入這個低開銷內存分析工具是爲了達到以下目的:

  • 足夠低的開銷,能夠默認且一直開啓
  • 能經過定義好的程序接口訪問
  • 可以對全部堆分配區域進行採樣
  • 能給出正在和未被使用的 Java 對象信息

對用戶來講,瞭解它們堆裏的內存分佈是很是重要的,特別是遇到生產環境中出現的高 CPU、高內存佔用率的狀況。目前有一些已經開源的工具,容許用戶分析應用程序中的堆使用狀況,好比:Java Flight Recorder、jmap、YourKit 以及 VisualVM tools.。可是這些工具都有一個明顯的不足之處:沒法獲得對象的分配位置,headp dump 以及 heap histogram 中都沒有包含對象分配的具體信息,可是這些信息對於調試內存問題相當重要,由於它可以告訴開發人員他們的代碼中發生的高內存分配的確切位置,並根據實際源碼來分析具體問題,這也是 Java 11 中引入這種低開銷堆分配採樣方法的緣由。

支持 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,從新定義瞭如下新標準算法名稱:

  1. TLS 協議版本名稱:TLSv1.3
  2. SSLContext 算法名稱:TLSv1.3
  3. TLS 1.3 的 TLS 密碼套件名稱:TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384
  4. 用於 X509KeyManager 的 keyType:RSASSA-PSS
  5. 用於 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 以前,須要考慮以下幾個兼容性問題:

  1. TLS 1.3 使用半關閉策略,而 TLS 1.2 以及以前版本使用雙工關閉策略,對於依賴於雙工關閉策略的應用程序,升級到 TLS 1.3 時可能存在兼容性問題。
  2. TLS 1.3 使用預約義的簽名算法進行證書身份驗證,但實際場景中應用程序可能會使用不被支持的簽名算法。
  3. TLS 1.3 再支持 DSA 簽名算法,若是在服務器端配置爲僅使用 DSA 證書,則沒法升級到 TLS 1.3。
  4. TLS 1.3 支持的加密套件與 TLS 1.2 和早期版本不一樣,若應用程序硬編碼了加密算法單元,則在升級的過程當中須要修改相應代碼才能升級使用 TLS 1.3。
  5. TLS 1.3 版本的 session 用行爲及祕鑰更新行爲與 1.2 及以前的版本不一樣,若應用依賴於 TLS 協議的握手過程細節,則須要注意。

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 算法相比,改進很是明顯。

圖 1. 回收算法停頓時間對比

本圖片引用自:The Z Garbage Collector - An Introduction

不過目前 ZGC 還處於實驗階段,目前只在 Linux/x64 上可用,若是有足夠的需求,未來可能會增長對其餘平臺的支持。同時做爲實驗性功能的 ZGC 將不會出如今 JDK 構建中,除非在編譯時使用 configure 參數:--with-jvm-features=zgc 顯式啓用。

在實驗階段,編譯完成以後,已經火燒眉毛的想試試 ZGC,須要配置如下 JVM 參數,才能使用 ZGC,具體啓動 ZGC 參數以下:

-XX:+ UnlockExperimentalVMOptions -XX:+ UseZGC -Xmx10g

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

  • 對象的分配速率,要保證在 GC 的時候,堆中有足夠的內存分配新對象。
  • 通常來講,給 ZGC 的內存越多越好,可是也不能浪費內存,因此要找到一個平衡。

飛行記錄器

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

Java 語言中的飛行記錄器相似飛機上的黑盒子,是一種低開銷的事件信息收集框架,主要用於對應用程序和 JVM 進行故障檢查、分析。飛行記錄器記錄的主要數據源於應用程序、JVM 和 OS,這些事件信息保存在單獨的事件記錄文件中,故障發生後,可以從事件記錄文件中提取出有用信息對故障進行分析。

啓用飛行記錄器參數以下:

-XX:StartFlightRecording

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

清單 2. 飛行記錄器啓動、配置參數示例
1
2
3
$ jcmd < pid > JFR.start
$ jcmd < pid > JFR.dump filename=recording.jfr
$ jcmd < pid > JFR.stop

JFR 使用測試:

清單 3. JFR 使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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. 飛行記錄器分析示例
1
2
3
4
5
6
7
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"));
     }
}

動態類文件常量

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

過 Java 開發人員一般不會注意到此功能,由於它隱藏在 Java 字節代碼中。經過使用 invokedynamic,能夠延遲方法調用的綁定,直到第一次調用。例如,Java 語言使用該技術來實現 Lambda 表達式,這些表達式僅在首次使用時才顯示出來。這樣作,invokedynamic 已經演變成一種必不可少的語言功能。

Java 11 引入了相似的機制,擴展了 Java 文件格式,以支持新的常量池:CONSTANT_Dynamic,它在初始化的時候,像 invokedynamic

令生成代理方法同樣,委託給 bootstrap 方法進行初始化建立,對上層軟件沒有很大的影響,下降開發新形式的可實現類文件約束帶來的成本和干擾。

結束語

Java 在更新發布週期爲每半年發佈一次以後,在合併關鍵特性、快速獲得開發者反饋等方面,作得愈來愈好。Java 11 版本的發佈也帶來了很多新特性和功能加強、性能提高、基礎能力的全面進步和突破,本文針對其中對使用人員影響重大的以及主要的特性作了介紹。Java 12 即將到來,您準備好了嗎?

本文僅表明做者我的觀點,不表明其所在單位的意見,若有不足之處,還望您可以海涵。但願您可以反饋意見,交流心得,一同進步。

參考資源

 

jdk11自 jdk8 後的首個長期支持版本,很是值得你們的關注。

轉自:https://www.ibm.com/developerworks/cn/java/the-new-features-of-Java-11/index.html

相關文章
相關標籤/搜索