Java 12 已如期於 3 月 19 日正式發佈,這次更新是 Java 11 這一長期支持版本發佈以後的一次常規更新,截至目前,Java 半年爲發佈週期,而且不會跳票承諾的發佈模式,已經成功運行一年多了。經過這樣的方式,Java 開發團隊可以將一些重要特性儘早的合併到 Java Release 版本中,以便快速獲得開發者的反饋,避免出現相似 Java 9 發佈時的兩次延期的狀況。html
Java 12 早在 2018 年 12 月便進入了 Rampdown Phase One 階段,這意味着該版本全部新的功能特性被凍結,不會再加入更多的 JEP。該階段將持續大概一個月,主要修復 P1-P3 級錯誤。主要時間節點以下:java
2018-12-13 Rampdown 第一階段 ( 從主線分離 )
2019-01-17 Rampdown 第二階段
2019-02-07 發佈候選階段
2019-03-19 正式發佈網絡
本文主要針對 Java 12 中的新特性展開介紹,讓您快速瞭解 Java 12 帶來的變化。併發
Java 12 中引入一個新的垃圾收集器:Shenandoah,它是做爲一中低停頓時間的垃圾收集器而引入到 Java 12 中的,其工做原理是經過與 Java 應用程序中的執行線程同時運行,用以執行其垃圾收集、內存回收任務,經過這種運行方式,給虛擬機帶來短暫的停頓時間。性能
Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣佈進行的一項垃圾收集器研究項目,旨在針對 JVM 上的內存收回實現低停頓的需求。該設計將與應用程序線程併發,經過交換 CPU 併發週期和空間以改善停頓時間,使得垃圾回收器執行線程可以在 Java 線程運行時進行堆壓縮,而且標記和整理可以同時進行,所以避免了在大多數 JVM 垃圾收集器中所遇到的問題。測試
據 Red Hat 研發 Shenandoah 團隊對外宣稱,Shenandoah 垃圾回收器的暫停時間與堆大小無關,這意味着不管將堆設置爲 200 MB 仍是 200 GB,都將擁有一致的系統暫停時間,不過實際使用性能將取決於實際工做堆的大小和工做負載。優化
圖 1. Shenandoah GC 工做週期以下所示ui
上圖對應工做週期以下:spa
須要瞭解不是惟有 GC 停頓可能致使常規應用程序響應時間比較長。具備較長的 GC 停頓時間會致使系統響應慢的問題,但響應時間慢並不是必定是 GC 停頓時間長致使的,隊列延遲、網絡延遲、其餘依賴服務延遲和操做提供調度程序抖動等均可能致使響應變慢。使用 Shenandoah 時須要全面瞭解系統運行狀況,綜合分析系統響應時間。各類 GC 工做負載對好比下所示:操作系統
圖 2. 各類 GC 工做負載對比
-XX:+AlwaysPreTouch:使用全部可用的內存分頁,減小系統運行停頓,爲避免運行時性能損失。
-Xmx == -Xmsv:設置初始堆大小與最大值一致,能夠減輕伸縮堆大小帶來的壓力,與 AlwaysPreTouch 參數配合使用,在啓動時提交全部內存,避免在最終使用中出現系統停頓。
-XX:+ UseTransparentHugePages:可以大大提升大堆的性能,同時建議在 Linux 上使用時將 /sys/kernel/mm/transparent_hugepage/enabled 和 /sys/kernel/mm/transparent_hugepage/defragv 設置爲:madvise,同時與 AlwaysPreTouch 一塊兒使用時,init 和 shutdownv 速度會更快,由於它將使用更大的頁面進行預處理。
-XX:+UseNUMA:雖然 Shenandoah 還沒有明確支持 NUMA(Non-Uniform Memory Access),但最好啓用此功能以在多插槽主機上啓用 NUMA 交錯。與 AlwaysPreTouch 相結合,它提供了比默認配置更好的性能。
-XX:+DisableExplicitGC:忽略代碼中的 System.gc() 調用。當用戶在代碼中調用 System.gc() 時會強制 Shenandoah 執行 STW Full GC ,應禁用它以防止執行此操做,另外還可使用 -XX:+ExplicitGCInvokesConcurrent,在 調用 System.gc() 時執行 CMS GC 而不是 Full GC,建議在有 System.gc() 調用的狀況下使用。
不過目前 Shenandoah 垃圾回收器還被標記爲實驗項目,須要使用參數:- XX:+UnlockExperimentalVMOptions 啓用。更多有關如何配置、調試 Shenandoah 的信息,請參閱 henandoah wiki。
Java 12 中添加一套新的基本的微基準測試套件,該套微基準測試套件基於 JMH(Java Microbenchmark Harness),使開發人員能夠輕鬆運行現有的微基準測試並建立新的基準測試,其目標在於提供一個穩定且優化過的基準,其中包括將近 100 個基準測試的初始集合,而且可以輕鬆添加新基準、更新基準測試和提升查找已有基準測試的便利性。
微基準套件與 JDK 源代碼位於同一個目錄中,而且在構建後將生成單個 Jar 文件。但它是一個單獨的項目,在支持構建期間不會執行,以方便開發人員和其餘對構建微基準套件不感興趣的人在構建時花費比較少的構建時間。
要構建微基準套件,用戶須要運行命令:make build-microbenchmark,相似的命令還有:make test TEST="micro:java.lang.invoke" 將使用默認設置運行 java.lang.invoke 相關的微基準測試。關於配置本地環境能夠參照文檔 docs/testing.md|html。
Java 11 以及以前 Java 版本中的 Switch 語句是按照相似 C、C++ 這樣的語言來設計的,在默認狀況下支持 fall-through 語法。雖然這種傳統的控制流一般用於編寫低級代碼,但 Switch 控制語句一般運用在高級別語言環境下的,所以其容易出錯性掩蓋其靈活性。
在 Java 12 中從新拓展了 Switch 讓它具有了新的能力,經過擴展示有的 Switch 語句,可將其做爲加強版的 Switch 語句或稱爲 "Switch 表達式"來寫出更加簡化的代碼。
Switch 表達式也是做爲預覽語言功能的第一個語言改動被引入新版 Java 中來的,預覽語言功能的想法是在 2018 年初被引入 Java 中的,本質上講,這是一種引入新特性的測試版的方法。經過這種方式,可以根據用戶反饋進行升級、更改,在極端狀況下,若是沒有被很好的接納,則能夠徹底刪除該功能。預覽功能的關鍵在於它們沒有被包含在 Java SE 規範中。
在 Java 11 以及以前版本中傳統形式的 Switch 語句寫法以下:
清單 1. Switch 語句示例
int dayNumber; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: dayNumber = 6; break; case TUESDAY: dayNumber = 7; break; case THURSDAY: case SATURDAY: dayNumber = 8; break; case WEDNESDAY: dayNumber = 9; break; default: throw new IllegalStateException("Huh? " + day); }
上面代碼中多處出現 break 語句,顯得代碼比較冗餘,同時若是某處漏寫一段 break 語句,將致使程序一直向下穿透執行的邏輯錯誤,出現異常結果,同時這種寫法比較繁瑣,也容易出問題。
換作 Java 12 中的 Switch 表達式,上述語句寫法以下:
清單 2. Switch 表達式示例
int dayNumber = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> throw new IllegalStateException("Huh? " + day); }
使用 Java 12 中 Switch 表達式的寫法,省去了 break 語句,避免了因少些 break 而出錯,同時將多個 case 合併到一行,顯得簡潔、清晰也更加優雅的表達邏輯分支,其具體寫法就是將以前的 case 語句表成了:case L ->,即若是條件匹配 case L,則執行 標籤右側的代碼 ,同時標籤右側的代碼段只能是表達式、代碼塊或 throw 語句。爲了保持兼容性,case 條件語句中依然可使用字符 : ,這時 fall-through 規則依然有效的,即不能省略原有的 break 語句,可是同一個 Switch 結構裏不能混用 -> 和 : ,不然會有編譯錯誤。而且簡化後的 Switch 代碼塊中定義的局部變量,其做用域就限制在代碼塊中,而不是蔓延到整個 Switch 結構,也不用根據不一樣的判斷條件來給變量賦值。
Java 11 以及以前版本中,Switch 表達式支持下面類型: byte、char、short、int、Byte、Character、Short、Integer、enum、tring,在將來的某個 Java 版本有可能會容許支持 float、double 和 long (以及上面類型的封裝類型)。
Java 12 中引入 JVM 常量 API,用來更容易地對關鍵類文件 (key class-file) 和運行時構件(artefact)的名義描述 (nominal description) 進行建模,特別是對那些從常量池加載的常量,這是一項很是技術性的變化,可以以更簡單、標準的方式處理可加載常量。
此項改進主要在新的 java.lang.invoke.constant 包中定義了一系列基於值的符號引用類型,可以描述每種可加載常量。符號引用以純粹 nominal 的形式描述可加載常量,與類加載或可訪問 性上下文分開。同時有些類能夠做爲本身的符號引用(例如 String),而對於可連接常量,另外定義了一系列符號引用類型,具體包括: ClassDesc (Class 的可加載常量標稱描述符) ,MethodTypeDesc(方法類型常量標稱描述符) ,MethodHandleDesc (方法句柄常量標稱描述符) 和 DynamicConstantDesc (動態常量標稱描述符) ,它們包含描述這些常量的 nominal 信息。
Java 12 中將只保留一套 AArch64 實現,刪除全部與 arm64 實現相關的代碼,只保留 32 位 ARM 端口和 64 位 aarch64 的端口。刪除此套實現將容許全部開發人員將目標集中在剩下的這個 64 位 ARM 實現上,消除維護兩套端口所需的重複工做。
當前 Java 11 中存在兩套 64 位 AArch64 端口,它們主要存在於 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目錄中。這兩套代碼中都實現了 AArch64,Java 12 中將刪除目錄 open/src/hotspot/cpu/arm 中關於 64-bit 的這套實現,只保留其中有關 32-bit 的實現,餘下目錄的 open/src/hotspot/cpu/aarch64 代碼部分就成了 AArch64 的默認實現。
類數據共享機制 (Class Data Sharing ,簡稱 CDS) ,容許將一組類預處理爲共享歸檔文件,以便在運行時可以進行內存映射以減小 Java 程序的啓動時間,當多個 Java 虛擬機(JVM)共享相同的歸檔文件時,還能夠減小動態內存的佔用量,同時減小多個虛擬機在同一個物理或虛擬的機器上運行時的資源佔用。
自 Java 8 以來,在基本 CDS 功能上進行了許多加強、改進,啓用 CDS 後應用的啓動時間和內存佔用量顯着減小。使用 Java 11 早期版本在 64 位 Linux 平臺上運行 HelloWorld 進行測試,測試結果顯示啓動時間縮短有 32 %,同時在其餘 64 位平臺上,也有相似或更高的啓動性能提高。
Java 12 針對 64 位平臺下的 JDK 構建過程進行了加強改進,使其默認生成類數據共享(CDS)歸檔,以進一步達到改進應用程序的啓動時間的目的,同時也避免了須要手動運行:-Xshare:dump 的須要,修改後的 JDK 將在 lib/server 目錄中保留構建時生成的 CDS 存檔。
固然若是須要,也能夠添加其餘 GC 參數,來調整堆大小等,以得到更優的內存分佈狀況,同時用戶也能夠像以前同樣建立自定義的 CDS 存檔文件。
G1 是垃圾收集器,設計用於具備大量內存的多處理器機器,提升了垃圾回收效率。該垃圾收集器 設計的主要目標之一是知足用戶設置的預期的 JVM 停頓時間,G1 採用一個高級分析引擎來選擇在收集期間要處理的工做量,此選擇過程的結果是一組稱爲 GC 回收集的區域。一旦收集器肯定了 GC 回收集 而且 GC 回收、整理工做已經開始,則 G1 收集器必須完成收集集合集的全部區域中的全部活動對象以後才能中止;可是若是收集器選擇過大的 GC 回收集,可能會致使 G1 回收器停頓時間超過預期時間。
Java 12 中將把 GC 回收集(混合收集集合)拆分爲必需和可選兩部分,使 G1 垃圾回收器能停止垃圾回收過程。其中必需處理的部分包括 G1 垃圾收集器不能遞增處理的 GC 回收集的部分(如:年輕代),同時也能夠包含老年代以提升處理效率。將 GC 回收集拆分爲必需和可選部分時,須要爲可選 GC 回收集部分維護一些其餘數據,這會產生輕微的 CPU 開銷,但小於 1 %的變化,同時在 G1 回收器處理 GC 回收集期間,本機內存使用率也可能會增長,使用上述狀況只適用於包含可選 GC 回收部分的 GC 混合回收集合。
在 G1 垃圾回收器完成收集須要必需回收的部分以後,便開始收集可選的部分,若是還有時間的話,可是粗粒度的處理,可選部分的處理粒度取決於剩餘的時間,一次只能處理可選部分的一個子集區域。在完成可選收集部分的收集後,G1 垃圾回收器能夠根據剩餘時間決定是否中止收集。若是在處理完 必需處理的 部分後,屬於時間不足,總時間花銷接近預期時間,G1 垃圾回收器也能夠停止可選部分的回收以達到知足預期停頓時間的目標。
上節中介紹了 Java 12 中加強了 G1 垃圾收集器關於混合收集集合的處理策略,這節主要介紹在 Java 12 中同時也對 G1 垃圾回收器進行了改進,使其可以在空閒時自動將 Java 堆內存返還給操做系統,這也是 Java 12 中的另一項重大改進。
目前 Java 11 版本中包含的 G1 垃圾收集器 暫時沒法及時將已提交的 Java 堆內存返回給操做系統, G1 垃圾收集器僅在進行完整 GC (Full GC) 或併發處理週期時才能將 Java 堆返回內存。因爲 G1 回收器儘量避免完整 GC,而且只觸發基於 Java 堆佔用和分配活動的併發週期,所以在許多狀況下 G 1 垃圾回收器不能回收 Java 堆內存,除非有外部強制執行。
在使用雲平臺的容器環境中,這種不利之處特別明顯。即便在虛擬機不活動,但若是仍然使用其分配的內存資源,哪怕是其中的一小部分,G1 回收器也仍將保留全部已分配的 Java 堆內存。而這將致使用戶須要始終爲全部資源付費,哪怕是實際並未用到,而云提供商也沒法充分利用其硬件。若是在次期間虛擬機可以檢測到 Java 堆內存的實際使用狀況,並在利用空閒時間自動將 Java 堆內存返還,則二者都將受益。
爲了儘量的向操做系統返回空閒內存,G1 垃圾收集器將在應用程序不活動期間按期生成或持續循環檢查總體 Java 堆使用狀況,以便 G 1 垃圾收集器可以更及時的將 Java 堆中不使用內存部分返還給操做系統。對於長時間處於空閒狀態的應用程序,此項改進將使 JVM 的內存利用率更加高效。
若是應用程序爲非活動狀態,在下面兩種狀況下,G1 回收器會觸發按期垃圾收集:
自上次垃圾回收完成 以來已超過 G1PeriodicGCInterva l 毫秒, 而且此時沒有正在進行的垃圾回收任務。若是 G1PeriodicGCInterval 值爲零表示禁用快速回收內存的按期垃圾收集。
應用所在主機系統上執行方法 getloadavg(),一分鐘內系統返回的平均負載值低於 G1PeriodicGCSystemLoadThreshold。若是 G1PeriodicGCSystemLoadThreshold 值爲零,則此條件不生效。
若是不知足上述條件中的任何一個,則取消當期的按期垃圾回收。等一個 G1PeriodicGCInterval 時間週期後,將從新考慮是否執行按期垃圾回收。
G1 按期垃圾收集的類型根據 G1PeriodicGCInvokesConcurrent 參數的值肯定:若是設置值了,G1 垃圾回收器將繼續上一個或者啓動一個新併發週期;若是沒有設置值,則 G1 回收器將執行一個完整的 GC。在每次一次 GC 回收末尾,G1 回收器將調整當前的 Java 堆大小,此時便有可能會將未使用內存返還給操做系統。新的 Java 堆內存大小根據現有配置肯定,具體包括下列配置:- XX:MinHeapFreeRatio、-XX:MaxHeapFreeRatio、-Xms、-Xmx。
默認狀況下,G1 回收器在按期垃圾回收期間新啓動或繼續上一輪併發週期,將最大限度地減小應用程序的中斷。若是按期垃圾收集嚴重影響程序執行,則須要考慮整個系統 CPU 負載,或讓用戶禁用按期垃圾收集。
原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。