Java11 新特性
Java 11新特性的詳細解釋。JDK 11已經于 2018年9月25日正式發布,那么Java 11主要包含哪些新特性呢?
JDK 11是Java SE 11平臺版本11的開源參考實現,由JSR 384在Java Community Process中指定。
阿里巴巴是中國唯一的JCP委員會成員公司,參與Java規范制定。
該版本的功能和時間表是通過JEP流程提出和跟蹤的,并由JEP 2.0提案進行了修訂。 使用JDK Release Process(JEP 3)生成發布。
Java11 主要新特性 詳解
1、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. 回收算法停頓時間對比
不過目前 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 的內存越多越好,但是也不能浪費內存,所以要找到一個平衡。
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 Flow 的概念也在 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 表達式如下:
(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)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、飛行記錄器
飛行記錄器之前是商業版 JDK 的一項分析工具,但在 Java 11 中,其代碼被包含到公開代碼庫中,這樣所有人都能使用該功能了。
Java 語言中的飛行記錄器類似飛機上的黑盒子,是一種低開銷的事件信息收集框架,主要用于對應用程序和 JVM 進行故障檢查、分析。飛行記錄器記錄的主要數據源于應用程序、JVM 和 OS,這些事件信息保存在單獨的事件記錄文件中,故障發生后,能夠從事件記錄文件中提取出有用信息對故障進行分析。
啟用飛行記錄器參數如下:
-XX:StartFlightRecording也可以使用 bin/jcmd 工具啟動和配置飛行記錄器:
清單 2. 飛行記錄器啟動、配置參數示例
$ jcmd <pid> JFR.start $ jcmd <pid> JFR.dump filename=recording.jfr $ jcmd <pid> JFR.stopJFR 使用測試:
清單 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"));} }10、動態類文件常量
為了使 JVM 對動態語言更具吸引力,Java 的第七個版本已將 invokedynamic 引入其指令集。
過 Java 開發人員通常不會注意到此功能,因為它隱藏在 Java 字節代碼中。通過使用 invokedynamic,可以延遲方法調用的綁定,直到第一次調用。例如,Java 語言使用該技術來實現 Lambda 表達式,這些表達式僅在首次使用時才顯示出來。這樣做,invokedynamic 已經演變成一種必不可少的語言功能。
Java 11 引入了類似的機制,擴展了 Java 文件格式,以支持新的常量池:CONSTANT_Dynamic,它在初始化的時候,像 invokedynamic
令生成代理方法一樣,委托給 bootstrap 方法進行初始化創建,對上層軟件沒有很大的影響,降低開發新形式的可實現類文件約束帶來的成本和干擾。
結束語
Java 在更新發布周期為每半年發布一次之后,在合并關鍵特性、快速得到開發者反饋等方面,做得越來越好。Java 11 版本的發布也帶來了不少新特性和功能增強、性能提升、基礎能力的全面進步和突破,本文針對其中對使用人員影響重大的以及主要的特性做了介紹。Java 12 即將到來,你準備好了嗎?
總結
以上是生活随笔為你收集整理的Java11 新特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java10 新特性
- 下一篇: Java垃圾回收日志解析