你想了解的JDK 10版本更新都在這裡

我沒有三顆心臟發表於2020-08-21

  • 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 「幫助新朋友快速高質量的學習」
  • 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關注 + 留言 + 分享」,文末有完整的獲取連結,您的支援是我前進的最大的動力!

特性總覽

以下是 Java 10 中的引入的部分新特性。關於 Java 10 新特性更詳細的介紹可參考這裡

  • 基於時間的發行版本控制(JEP 322)
  • 區域性變數型別推斷(JEP 286)
  • 試驗性 JIT 編譯器(JEP 317)
  • 應用程式類資料共享(JEP 310)
  • 用於 G1 的並行 Full GC(JEP 307)
  • 清理垃圾收集器介面(JEP 304)
  • 其他 Unicode 語言標籤擴充套件(JEP 314)
  • 根證照(JEP 319)
  • 執行緒本地握手(JEP 312)
  • 備用儲存裝置上的堆分配(JEP 316)
  • 刪除本機頭生成工具——javah(JEP 313)
  • 將JDK森林合併到單個儲存庫中(JEP 296)
  • API 變更

一. 基於時間的發行版本控制(JEP 322)

發行版本號的前世今生

自 Java 江山易主,JDK 發行版本的字串命名方式一直是一個耐人尋味的話題。

單就下載 JDK 時,所看到的簡短版本字串形式來說,在 7u40 版本之前,u 之後的數字,代表了 JDK 釋出以來的第幾個修正版本,然而 Oracle 改變規則,為了彰顯出安全之類的重大 修補(Cirtical Patch Updates)版本,採用 奇數 命名,而 Bug 修復、API 修改之類的 維護版本,則採用 偶數(另有版本號 $MAJOR.$MINOR.$SECURITY 的格式來區分 Bug 修正和 API 修改)

為此,之前既有的 JDK 6/7 釋出版本,還被重新命名。(下方演示)

   Actual                    Hypothetical
Release Type           long               short
------------           ------------------------ 
Security 2013/06       1.7.0_25-b15       7u25
Minor    2013/09       1.7.0_40-b43       7u40
Security 2013/10       1.7.0_45-b18       7u45
Security 2014/01       1.7.0_51-b13       7u51
Minor    2014/05       1.7.0_60-b19       7u60

就結論而言,重新命名之後,從 7u96u37 開始,就可以從奇偶數來判別是否為重大修補版本;至於 7u40 之後的版本,重大修補版本 是基於 5 的倍數,遇偶數加一,而 維護版本 則會是 20 的倍數。如此(不直觀)的命名方式,被規範在了 JEP223 中。(隨著 JDK 9 一起釋出的)

然而,自 JDK 8 釋出之後,Oracle 的 Java 架構師 Mark Reinhold 就希望,未來 Java 釋出可以基於時間,以半年為週期,持續釋出新版本,讓一些有用的小特性,也能被開發者使用,因此,JEP 223 的規範就不再適用了,而在 JDK 9 釋出後,他們針對新版本曾經提出過基於 $YEAR.$MONTH 格式,然而收到了社群極大的反對,為此,還提出了三個替代方案,收集各方的意見。(https://goo.gl/7CA8B3)

那麼,Java 下一個特性版本是 8.31803?還是 10?調查結果顯示,社群大多數都支援 10,Stephen Colebourne 也發出請求(https://goo.gl/i5J44T),並表示 Java 不像 Ubuntu 這類作業系統,基於 $YEAR.$MONTH 並不合適。

最終,Oracle 採用了 $FEATURE.$INTERIM.$UPDATE.$PATCH 這樣的方案,並規定在了 JEP 322 中。

JEP 322 新模式解讀

通過採用基於時間的發行週期,Oracle 更改了 Java SE 平臺和 JDK 的版本字串方案以及相關的版本資訊,以適用於當前和將來的基於時間的發行模型。

版本號的新模式是:

$FEATURE.$INTERIM.$UPDATE.$PATCH

  • $ FEATURE:計數器將每 6 個月遞增一次,並基於功能釋出版本,例如:JDK 10,JDK 11。
  • $ INTERIM:對於包含相容錯誤修復和增強功能但沒有不相容更改的非功能版本,計數器將增加。通常,這將是零,因為六個月內不會有任何臨時釋出。這保留了對釋出模型的將來修訂。
  • $ UPDATE:計數器將增加,用於解決安全問題,迴歸和較新功能中的錯誤的相容更新版本。此功能會在功能釋出後一個月更新,此後每三個月更新一次。2018 年 4 月版本是 JDK 10.0.1,7 月版本是 JDK 10.0.2,依此類推
  • $ PATCH:計數器將增加以用於緊急釋放以解決嚴重問題。

並新增了新的 API 以通過程式設計的方式獲取這些計數器:

Version version = Runtime.version();
version.feature();
version.interim();
version.update();
version.patch();

現在,讓我們來看一下返回版本資訊的 Java 啟動器:

$ java -version
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)

版本號格式為 10,因為沒有其他計數器為零。釋出日期已新增。可以將 18.3 理解為 Year 2018&3rd Month,版本 10 + 46 是版本 1046 版本。對於 JDK 10.0.1 的假設版本 93,版本將為 10.0.1 + 93

因此,對於 Java 9 來說,目前可以看到的版本,會是 9.0.4 這樣的格式,至於 2018 年 3 月釋出的特性版本為 10,到了 9 月提供的新版本,就是 11,JDK 11 預計會是長期支援版本,所以,在版本字串上,也會特別顯示 LTS(long-term support)。

二. 區域性變數型別推斷(JEP 286)

概述

JDK 10 中最明顯的增強功能之一是使用初始化程式對區域性變數進行型別推斷。

在 Java 9 之前,我們必須明確寫出區域性變數的型別,並確保它與用於初始化它的初始化程式相容:

String message = "Good bye, Java 9";

在 Java 10 中,這是我們可以宣告區域性變數的方式:

@Test
public void whenVarInitWithString_thenGetStringTypeVar() {
    var message = "Hello, Java 10";
    assertTrue(message instanceof String);
}

我們沒有提供 message 的具體型別,相反,我們把 message 標記為了 var,編譯器將從右側的初始化程式的型別推斷出 message 的型別。(上面的例子中 messageString 型別)

請注意,此功能僅適用於帶有初始化程式的區域性變數。它不能用於成員變數、方法引數、返回型別等——初始化程式是必須的,否則,編譯器無法推斷出其型別。

這個功能有助於我們減少樣板式的程式碼,例如:

Map<Integer, String> map = new HashMap<>();

現在可以改寫為:

var idToNameMap = new HashMap<Integer, String>();

這也有助於我們把重點放在變數名,而不是變數型別上。

要注意的另一件事是 var 不是關鍵字——這確保了使用 var 作為函式或變數名的程式的向後相容性。var 是一個保留型別名,就像 int 一樣。

最後,使用 var 不會增加執行時的開銷,也不會使 Java 稱為動態型別的語言。變數的型別仍然是在編譯時進行判斷,以後也無法更改。

非法使用 var 的情況解析

1、如果沒有初始化程式,var 將無法工作:

var n; // error: cannot use 'var' on variable without initializer

2、如果將其初始化為 null,也不會起作用:

var emptyList = null; // error: variable initializer is 'null'

3、不適用於非區域性變數:

public var word = "hello"; // error: 'var' is not allowed here

4、Lambda 表示式需要顯式的型別,因此無法使用 var

var p = (String s) -> s.length() > 10; // error: lambda expression needs an explicit target-type

5、陣列初始化程式也不支援:

var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type

使用 var 的準則

在某些情況下,我們可以合法使用 var,但這樣做並不是一個好主意。

例如,在程式碼的可讀性降低的情況下:

var result = obj.prcoess();

在這裡,儘管可以合法使用 var,但很難理解 process() 返回的型別,從而讓程式碼的可讀性降低。

java.net(OpenJDK 官網)上專門有一篇文章介紹了 Java 中的區域性變數型別推斷的書寫準則,該文章討論了在使用此功能時應該注意的姿勢和如何使用的一些良好建議。

另外,最好避免使用 var 的另一種情況是在流水線較長的流中:

var x = emp.getProjects.stream()
  .findFirst()
  .map(String::length)
  .orElse(0);

另外,將 var 與不可引用型別一起使用可能會導致意外錯誤。

比如,如果我們將 var 與匿名類例項一起使用:

@Test
public void whenVarInitWithAnonymous_thenGetAnonymousType() {
    var obj = new Object() {};
    assertFalse(obj.getClass().equals(Object.class));
}

現在,如果我們嘗試將另一個 Object 分配給 obj,則會出現編譯錯誤:

obj = new Object(); // error: Object cannot be converted to <anonymous Object>

這是因為 obj 的推斷型別不是 Object。

三. 試驗性 JIT 編譯器(JEP 317)

Graal 是用Java編寫的,與 HotSpot JVM 整合的動態編譯器。它專注於高效能和可擴充套件性。它也是 JDK 9 中引入的實驗性 Ahead-of-Time(AOT)編譯器的基礎。

JDK 10 使 Graal 編譯器可以用作 Linux / x64 平臺上的實驗性 JIT 編譯器。

要將 Graal 用作 JIT 編譯器,請在 Java 命令列上使用以下選項:

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

請注意,這是一個實驗性功能,我們不一定會獲得比現有JIT編譯器更好的效能。

想要了解更多內容的童鞋請參考 Chris Seaton 的演講:https://chrisseaton.com/truffleruby/jokerconf17/

(ps:長文 + 特別底層警告..)

四. 應用程式類資料共享(JEP 310)

JDK 5 中引入的類資料共享允許將一組類預處理成共享的歸檔檔案,然後在執行時對其進行記憶體對映,以減少啟動時間,這還可以在多個 JVM 共享相同的歸檔檔案時減少動態記憶體佔用。

CDS 只允許引導類裝入器,將該特性限制為系統類。應用程式 CDS (AppCDS)擴充套件了 CDS 以允許內建的系統類裝入器。內建的平臺類裝入器和用於裝入歸檔類的自定義類裝入器。這使得對應用程式類使用該特性成為可能。

我們可以使用以下步驟來使用這個功能:

1、獲取要存檔的類列表

以下命令會將HelloWorld 應用程式載入的類轉儲到hello.lst中

$ java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=hello.lst \ 
    -cp hello.jar HelloWorld

2、建立 AppCDS 存檔

以下命令使用hello.lst 作為輸入建立hello.js a

$ java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=hello.lst \
    -XX:SharedArchiveFile=hello.jsa -cp hello.jar

3、使用 AppCDS 存檔

以下命令以hello.jsa 作為輸入啟動HelloWorld 應用程式:

$ java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
    -cp hello.jar HelloWorld

AppCDS 是用於 JDK 8 和 JDK 9 的 Oracle JDK 中的一項商業功能。現在它是開源的,並且可以公開使用。

五. 用於 G1 的並行 Full GC(JEP 307)

G1 垃圾收集器是自 JDK 9 以來的預設垃圾收集器。但是,G1 的 Full GC 使用了單執行緒的 mark-sweep-compact 演算法。

它已 更改為 Java 10 中 的並行mark-sweep-compact演算法 ,有效地減少了 Full GC 期間的停滯時間。

六. 清理垃圾收集器介面(JEP 304)

這個 JEP 是未來的變化。通過引入公共垃圾收集器介面,它改善了不同垃圾收集器的程式碼隔離。

次更改為內部 GC 程式碼提供了更好的模組化。將來將有助於在不更改現有程式碼庫的情況下新增新 GC,還有助於刪除或保留以前的 GC。

官方的動機解釋:傳送門

當前,每個垃圾收集器實現都由其 src/hotspot/share/gc/$NAME 目錄內的原始檔組成,例如 G1 在中 src/hotspot/share/gc/g1,CMS 在 src/hotspot/share/gc/cms 等中。但是,在 HotSpot 中散佈著一些零散的資訊。例如,大多數 GC 需要某些障礙,這些障礙需要在執行時,直譯器 C1 和 C2 中實現。這些障礙並不包含在 GC 的具體目錄,但在共享直譯器,而不是實施,C1 和 C2 的原始碼(通常由長守衛if- else-chains)。同樣的問題也適用於診斷程式碼,例如 MemoryMXBeans。此原始碼佈局有幾個缺點:

  1. 對於GC開發人員,實施新的垃圾收集器需要有關所有這些地方的知識,以及如何擴充套件它們以滿足其特定需求的知識。

  2. 對於不是 GC 開發人員的 HotSpot 開發人員,在哪裡為給定 GC 找到特定的程式碼段會造成混亂。

  3. 在構建時很難排除特定的垃圾收集器。該#define INCLUD E_ALL_GCS長期以來建立與唯一內建序列收集JVM的一種方式,但這種機制變得過於呆板。

較乾淨的 GC 介面將使實現新的收集器更加容易,使程式碼更加清潔,並且在構建時排除一個或多個收集器也更加容易。新增一個新的垃圾收集器應該是實現一組有據可查的介面,而不是弄清 HotSpot 中所有需要更改的地方。

七. 其他Unicode語言標籤擴充套件(JEP 314)

此功能增強了 java.util.Locale 和相關 API,以實現 BCP 47 語言標籤的其他 Unicode 擴充套件。從 Java SE 9 開始,受支援的 BCP 47 U 語言副檔名是 "ca" 和 "nu"。該 JEP 將增加對以下附加擴充套件的支援:

  • cu(貨幣型別)
  • fw(一週的第一天)
  • rg(區域覆蓋)
  • tz(時區)

為了支援這些附加擴充套件,對以下各種 API 進行了更改以提供基於 U 或附加擴充套件的資訊:

java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider

八. 根證照(JEP 319)

cacerts 金鑰庫(迄今為止到目前為止是空的)旨在包含一組根證照,這些根證照可用於建立對各種安全協議所使用的證照鏈的信任。

結果,在 OpenJDK 構建中,諸如 TLS 之類的關鍵安全元件預設情況下不起作用。

藉助 Java 10,Oracle 將 Oracle Java SE Root CA 程式中 的根證照開源了 ,以使 OpenJDK 構建對開發人員更具吸引力,並減少了這些構建與 Oracle JDK 構建之間的差異。

九. 執行緒本地握手(JEP 312)

這是用於提高 JVM 效能的內部特性。

握手操作是線上程處於安全點狀態時為每個 JavaThread 執行的回撥。回撥由執行緒本身或 VM 執行緒執行,同時保持執行緒處於阻塞狀態。

這個特性提供了一種無需執行全域性 VM 安全點即可線上程上執行回撥的方法。使停止單個執行緒,而不是停止所有執行緒或不停止執行緒成為可能,而且代價低廉。

十. 備用儲存裝置上的堆分配(JEP 316)

應用程式的記憶體消耗越來越大,本地雲應用程式、記憶體中的資料庫、流應用程式都在增加。為了滿足這些服務,有各種可用的記憶體架構。這個特性增強了 HotSpot VM 在使用者指定的備用記憶體裝置(比如NV-DIMM)上分配 Java 物件堆的能力。

這個 JEP 的目標是具有與 DRAM 相同語義(包括原子操作的語義)的可選記憶體裝置,因此,可以在不更改現有應用程式程式碼的情況下,將其用於物件堆,而不是用於 DRAM。

十一. 刪除本機頭生成工具—javah (JEP 313)

這是一個從 JDK 中刪除 javah 工具的常規更改。工具功能是作為 JDK 8 的一部分在 javac 中新增的,它提供了在編譯時編寫使 javah 無用的本機標頭檔案的能力。

十二. 將JDK森林合併到單個儲存庫中(JEP 296)

多年來,有各種各樣的 Mercurial 儲存庫用於 JDK 程式碼基。不同的儲存庫確實提供了一些優勢,但它們也有各種操作上的缺點。作為這個變化的一部分,JDK 森林的許多儲存庫被合併到一個儲存庫中,以簡化和簡化開發。

十三. API 變更

Java 10 新增和刪除了 API。Java 9 引入了增強的棄用,其中某些 API 被標記為將在未來的版本中刪除。

於是這些 API 被刪除了:你可以在 這裡 找到被刪除的 API。

新增API: Java 10 中新增 73 個API。您可以在 這裡 找到新增的 API 並進行比較。

讓我們來看看對我們直接有用的部分。

不可修改集合的改進

Java 10 中有一些與不可修改集合相關的更改

copyOf()

java.util.Listjava.util.Mapjava.util.Set 都有了一個新的靜態方法 copyOf(Collection)

它返回給定 Collection 的不可修改的副本:

jshell> var originList = new ArrayList<String>();
originList ==> []

jshell> originList.add("歡迎關注公眾號:");
$2 ==> true

jshell> originList.add("我沒有三顆心臟");
$3 ==> true

jshell> var copyList = List.copyOf(originList)
copyList ==> [歡迎關注公眾號:, 我沒有三顆心臟]

jshell> originList.add("獲取更多精彩內容")
$5 ==> true

jshell> System.out.println(copyList)
[歡迎關注公眾號:, 我沒有三顆心臟]

jshell> copyList.add("獲取更多精彩內容")
|  異常錯誤 java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:73)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:77)
|        at (#7:1)

jshell>

toUnmodifiable()

java.util.Collectors 獲得其他方法來將 Stream 收集到不可修改的 List、Map 或 Set 中:

@Test(expected = UnsupportedOperationException.class)
public void whenModifyToUnmodifiableList_thenThrowsException() {
    List<Integer> evenList = someIntList.stream()
      .filter(i -> i % 2 == 0)
      .collect(Collectors.toUnmodifiableList());
    evenList.add(4);
}

任何嘗試修改此類集合的嘗試都會導致 java.lang.UnsupportedOperationException 執行時異常。

Optinal 新增方法 orElseThrow()

java.util.Optionaljava.util.OptionalDoublejava.util.OptionalIntjava.util.OptionalLong 都有一個新方法 orElseThrow(),它不接受任何引數,如果不存在任何值,則丟擲 NoSuchElementException

@Test
public void whenListContainsInteger_OrElseThrowReturnsInteger() {
    Integer firstEven = someIntList.stream()
      .filter(i -> i % 2 == 0)
      .findFirst()
      .orElseThrow();
    is(firstEven).equals(Integer.valueOf(2));
}

它與現有的 get() 方法同義,並且現在是它的首選替代方法。

參考資料

  1. OpenJDK 官方說明 - http://openjdk.java.net/projects/jdk/10/
  2. Java 10 Features | JournalDev - https://www.journaldev.com/20395/java-10-features
  3. 想跳舞的 Java | 林信良 - https://www.ithome.com.tw/voice/122249
  4. Java 10 Performance Improvements | Baeldung - https://www.baeldung.com/java-10-performance-improvements

文章推薦

  1. 這都JDK15了,JDK7還不瞭解? - https://www.wmyskxz.com/2020/08/18/java7-ban-ben-te-xing-xiang-jie/
  2. 全網最通透的 Java 8 版本特性講解 - https://www.wmyskxz.com/2020/08/19/java8-ban-ben-te-xing-xiang-jie/
  3. Java9的這些史詩級更新你都不知道? - https://www.wmyskxz.com/2020/08/20/java9-ban-ben-te-xing-xiang-jie/
  4. 「MoreThanJava」系列文集 - https://www.wmyskxz.com/categories/MoreThanJava/
  • 本文已收錄至我的 Github 程式設計師成長系列 【More Than Java】,學習,不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
  • 個人公眾號 :wmyskxz,個人獨立域名部落格:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!

非常感謝各位人才能 看到這裡,如果覺得本篇文章寫得不錯,覺得 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!

相關文章