Java 9 內部探索——版本架構,多版本 jar 包及其他

pijp9091 8年前發布 | 9K 次閱讀 Java9 Java開發

JShell

因為已經有很多人在談論 Jigsaw,因此在第一部分我們先跳過不去講它。在這一部分我們將會照本宣科地拿 JShell 做些事情, 這是 Java 的一個全新的 REPL (說到它能做的事情,例如你在一個地方敲入了 Java 代碼,有了它就可以馬上把代碼的運行結果計算出來)。如果你還并不(特別地)了解這個東西但又感覺有點興趣的話,可以看看 Robert Field 在 去年的 Devoxx Belgium 上提供的 這份不錯的介紹 。

新的版本字符串

讓我們先來個簡單的入門介紹: 版本名稱。

我嘗試過去理解 Java的版本命名模式 ,直到這樣做的時候才覺得值得去深究一番。它是從 1.0 和 1.1 版本的 JDK 開始的 – 這倆版本還算是那么回事兒,但這倆版本以后就越來越不那么像話了。版本 1.2 到 1.5 對商標進行了重命名,如 Java 2 這樣的,改變比較明顯(還記得J2SE 嗎 ? 其實指的就是 2 這個版本) 。到了  JDK 1.5 就很明顯可以看出上述的命令模式沒有真正起作用,因此 Sun就開始將它叫做 Java 5 了。圍繞 Java 6,整個自 Java 2 開始的命名創意被悄無聲息的埋沒了,不過這樣反而更讓人明白——我們簡單的叫它“Java X”就可以了。 (你是否知道 Java 版本,包含 Java 7 其實都有一個像  Tiger和Mustang這樣 很酷的工程名字?)

JVM 所報告的版本字符串并沒有做出修改——它們總會是 1.x.... 這樣的形式,不過現在有了 JEP 223 , 版本字串和命令模式做了對齊。如果檢查相關的系統屬性(見這里的 demo ), 輸出會是下面這樣的內容:

java.version: 9-ea
java.runtime.version: 9-ea+138-jigsaw-nightly-h5561-20161003
java.vm.version: 9-ea+138-jigsaw-nightly-h5561-20161003
java.specification.version: 9
java.vm.specification.version: 9

這并非過分顯示的信息,因為它是跑在一個早期可訪問的構建本上的。在將來 java.version 會報告像 9.1.2 這樣的字符串, 所遵循的是 $MAJOR.$MINOR.$SECURITY 模式:

  • $MAJOR 所標識的是 Oracle 計劃每兩到三年發布的主版本號。

  • $MINOR 所標識的是針對Bug修復以及一些其它需要定期跟進的更小一點的版本號——當主版本號發布時,這個標識就會被重置為零。

  • $SECURITY 則相當有意思 – 這個標識在每次發布中“含有包括為提升安全性而進行的重要修復”時就會要用到,并且在 $MINOR 變大的時候它并不會被 重置。

現在我們已經沒必要對這些字符串進行轉換了,因為有了 Version , 這是一個能為我們做這些事情的小類,很不錯。

Version version = Runtime.version();
System.out.println("Reported by runtime: " + version);
switch (version.major()) {
    case 9:
        System.out.println("Modularity!");
        break;
    case 10:
        System.out.println("Value Types!");
        break;

GNU 風格的命令行選項

當涉及到命令選項的語法時,Java 的各種工具就有點太過于形式各異了:

  • 有些會在 長版本選項前面 使用一個短橫線(-classpath),其它則會使用兩個(--gzip)

  • 有些用短橫線分隔單詞(--no-gzip),其它則沒有這樣做(-classpath)

  • 有些是單個字母形式的(-d), 其它則是兩個字母形式的(-cp, 真不知道那樣搞是為了啥?!)

  • 有些用等號賦值(-D<name>=<value>), 其它則需要一個空格(我可不會這樣做…)

相比之下,在 Linux 以及其它基于 GNU 的系統上幾乎所有工具的選項都使用的是相同的語法:

  • 長版本選項前面都是使用兩個短橫線

  • 單詞都用短橫線分隔

  • 選項縮寫都會使用一個短橫線并且只會包含一個字母

在一次不顧一切的魯莽行動中,Java 9 快刀斬亂麻,修改了所有的命令行選項以匹配上述這些規則!好吧,這個只是跟你開個玩笑… 不過 JEP 293 已經確定了一個準則來反映和適配這些規則,并且新的選項預期也會遵循這個準則。老的選項可能會在某些時候向這種更加干凈的語法遷移,但那并不是 Java 9 開發工作的一部分。JEP 包含了許多詳細內容以及示例 – 值得一讀。

擴展和更新

有很多 JEP(Java Expression Parser,Java表達式分析器)會隨 Java 9 發布,它們會帶來對已存在功能的提高擴展和更新。下面介紹它們,以主題為序,有些總結,有些細節。

Unicode

Java 自身可以用 UTF-16 (你的代碼里可以用 emoji)[譯者注:emoji 是 Unicode 的表情符號]來寫代碼,屬性文件則必須使用 ISO-8859-1。來看看,如果有一個像這樣的 config.properties 文件:

money = € / \u20AC

在 Java 8 中訪問這個文件:

money = a?? / €

JEP 226 終結了那個時代,不再需要進行  Unicode 轉義。Java 9 中同樣的代碼訪問這個文件會得到我們期望的結果:

money = € / €

(有一個 完整示例 ,不過我們的代碼高亮不太好用。)

值得注意的是,我們有很多種方法用于訪問屬性文件,但是只有通過 PropertyResourceBundle 訪問的這種方法被更新了。 JavaDoc 中的 API 文檔說明了如何精確的檢測編碼以及如何對其進行配置。默認配置是明智的,雖然它只是讓 API 在一般情況下  “可以工作”:

try (InputStream propertyFile = new FileInputStream("config.properties")) {
    PropertyResourceBundle properties = new PropertyResourceBundle(propertyFile);
    properties.getKeys().asIterator().forEachRemaining(key -> {
        String value = properties.getString(key);
        System.out.println(key + " = " + value);
    });} catch (IOException e) {
    e.printStackTrace();}

在 示例 中你可以找到使用了 Properties API 的代碼。如果你想在 Java 8 中運行它來進行比較,你會發現 Java 9 的 API 中有一個漂亮的小修改。這個小修改是為仍然在使用古老的 Enumeration 的可憐開發者而做。

在其它 Unicode 相關的新聞里,Java 9 支付了 Unicode 8.0 。耶!

圖形圖像

圖像 I/O 框架(在 javax . imageio 包中) 現在支持TIFF 了。Java 高級圖像處理(Java Advanced Imaging,JAI)項目 實現了對 TIFF 的讀寫。它已經完成標準化并移到javax.imageio.plugins.tiff 包中。

視網膜 HiDPI 屏幕為桌面 UI 帶來特有的挑戰。Java已經在 Mac 上處理好了這個問題,現在緊跟著開始適配 Linux 和 Windows。“窗口和 GUI 組件會基于平臺推薦而擁有一個合適的大小,在任何 HiDPI 設置下,默認的綻放都應該讓文本應保持高清,圖標和圖像在合適的顯示密度上應該光滑并盡可能地展現細節。”  

Linux 上 Java 桌面的三劍客(AWT、Swing 和 JavaFX)現在可以使用 GTK 3 。一開始JVM 會默認使用 GTK 2,只能通過 jdk.gtk.version 來配置使用  GTK 3。“交互性”需要 GTK 3,這個需求很早就能被探測到”。

JavaFX 使用過時的 GStreamer 版本,目前更新到了 1.4.4。新版本會提供 JavaFX 在播放媒體時的穩定性和性能。

HarfBuzz 是新的  OpenType 布局引擎,取代了已被停用的  ICU 。

安全性

SHA-3哈希算法已經實現了 SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。可以通過  MessageDigestAPI 使用它們。

在使用 SecureRandom(在任何 Java 版本中)時,你可以獲取操作系統的原生實現,或者 純 Java 實現的版本 。后者是“舊的基于 SHA-1 的 RNG 實現,它的健壯性不如被認可的   DRBG  [Deterministic Random Bit Generator] 機制。 ”既然是舊的,特別是嵌入式系統,它依賴于 Java 變化,它的安全性隨  NIST 800-90Ar1 所描述的 DRBG 機制得到了提升。SecureRandom API 已經被改進,可以傳遞參數以使用 DRBG 和將來的算法。 

Instantiation instantiation = DrbgParameters.instantiation(128, RESEED_ONLY, null);
SecureRandom random = SecureRandom.getInstance("DRBG", instantiation);

byte[] bytes = new byte[20]; random.nextBytes(bytes);</code></pre>

新的 Java 虛擬機特性

機器可以代替我們工作——我想知道是否有一天會實現?它會獲得一些新的特性,并被人們所喜愛,但一旦它在我們生活中出現,我們可能又要花上好幾年去適應它。如果不能實現的話,我現在先介紹另一個“新機器”—— insect  機器。

包含多個發行版的 JAR 包

可能你有時候會想為不同的 Java 運行版本寫代碼——在 Java 8 上做 一些事情 ,在 Java 9 上做 另外一些事情 。到現在為止,這處理起來都很棘手,不過 Java 9 解決了這個問題最重要的部分。現在所有不同的 Java 平臺都能創建,也能認識包多個發行版的 JAR 包,它會包含同一類型的不同版本,這些版本對應不同的 Java 版本。

來看個示例:

  1. 先創建三個類:Main、VersionDependent用于 Java8,還有 VersionDependentfor 用于 Java 9。后續的代碼提供一個用于打印的結果,內容是 “Java X version”,其中 X 是8 或 9。

  2. 然后編譯 Main 和 VersionDependent(用于 Java 8 的),結果放在目錄 out-mr/java8 上;編譯VersionDependent(Java 9 的版本) 放在 out-mr/java9 上。

  3. 現在開始有趣了,來看看怎么打包。下面的命令會創建一個 mr.jar,包含兩個 VersionDependent.class (分別來源于 out-mr/java-x 目錄)。因為結構明確,所以 java 能選擇正確的類:

    jar9 --create --file out-mr/mr.jar -C out-mr/java-8 . \    --release 9 -C out-mr/java-9 .
  4. 確實,使用 Java 8 運行 java -cp out-mr/mr.jar ...Main 會輸出 “Java 8 version” 而 使用 Java 9 運行會輸出 “Java 9 version”。

JAR 內部結構看起來像這樣:

└ org
    └ codefx ... (moar folders)
        ├ Main.class
        └ VersionDependent.class
└ META-INF
    └ versions
        └ 9
            └ org
                └ codefx ... (moar folders)
                    └ VersionDependent.class

Java 8 或更早的版本會在直接使用 org 下面的類,但新版本會使在 META-INFO/versions 子目錄中去找合適的內容來代替默認的。干凈利落。

統一的日志

調試 JVM,可能要找到應用崩潰或者性能低下的原因,這已經夠復雜了。而不同的日志系統有著完全不同的選項,這并不能讓事件變得簡單。幸好有即將通過的 JEP 158271 ,它帶來了新的命令行參數 -Xlog(現在不應該是 --log?)可以用來定義極盡詳細的日志級別。這里有一些可用的設置:

  • 每個消息可以有大量的標簽,這依賴于創建和使用它的子系統,如 aregc、模塊、oros等。可以選中單獨的標簽并對其應用其它設置。

  • 當然消息有等級 (error,warning,info,debug,trace,develop) 而且可以按等級進行過濾。

  • 可以為日志文件轉換設置輸出到 stdout、stderr 或者文件。

  • 然后還有 裝飾 – 附加在消息上的有用信息(pid、uptime、…、技術標簽和等級都是裝飾)。它們都可以被輸出。

這一些都可以由單獨的 -Xlog 選項完成。讓我們從簡單的記錄幾個標簽開始:

java9 -cp out-mr/mr.jar -Xlog:os,modules,gc ...Main

[0.002s][info][os] SafePoint Polling address: 0x00007feea4c96000 [0.002s][info][os] Memory Serialize Page address: 0x00007feea4c94000 [0.002s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22 [0.009s][info][gc] Using G1 Java 9 version</code></pre>

咦,難道沒有模塊信息?讓我們把那個標簽改為 debug(如果沒有指定等級,默認是 info):

java9 -cp out-mr/mr.jar -Xlog:os,modules=debug,gc org.codefx.demo.java9.internal.multi_release.Main

[0.002s][info][os] SafePoint Polling address: 0x00007f3054a22000 [0.002s][info][os] Memory Serialize Page address: 0x00007f3054a20000 [0.002s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22 [0.009s][info][gc] Using G1 [0.059s][debug][modules] set_bootloader_unnamed_module(): recording unnamed module for boot loader [0.063s][debug][modules] define_javabase_module(): Definition of module: java.base, version: 9-ea, location: jrt:/java.base, package #: 159 [... snip ... many, many more module messages ... ]</code></pre>

太多了,不過我們可以看到這樣:

[0.079s][info][modules,startuptime] Phase2 initialization, 0.0366552 secs

嘿,這是 info!為什么之前它沒有顯示出來?!Hey, that’sinfo! Why did it not show up before?! 令人驚異的是它有兩個標簽,在命令中只匹配其中 一個 標簽是不夠的——必須匹配 所有 標簽。我們可以通過 modules+startuptime 或者使用通配符來擴展匹配:

java9 -cp out-mr/mr.jar -Xlog:os,modules,gc ...Main

[0.002s][info][os] SafePoint Polling address: 0x00007f9c7f307000 [0.002s][info][os] Memory Serialize Page address: 0x00007f9c7f305000 [0.003s][info][os] HotSpot is running with glibc 2.22, NPTL 2.22 [0.007s][info][gc,heap] Heap region size: 1M [0.009s][info][gc ] Using G1 [0.009s][info][gc,heap,coops] Heap address: 0x00000006c6200000, size: 3998 MB, Compressed Oops mode: Zero based, Oop shift amount: 3 [0.077s][info][modules,startuptime] Phase2 initialization, 0.0367418 secs Java 9 version [0.090s][info][gc,heap,exit ] Heap [0.090s][info][gc,heap,exit ] garbage-first heap total 256000K, used 2048K [0x00000006c6200000, 0x00000006c63007d0, 0x00000007c0000000) [0.090s][info][gc,heap,exit ] region size 1024K, 3 young (3072K), 0 survivors (0K) [0.090s][info][gc,heap,exit ] Metaspace used 4225K, capacity 4532K, committed 4864K, reserved 1056768K [0.090s][info][gc,heap,exit ] class space used 414K, capacity 428K, committed 512K, reserved 1048576K</code></pre>

瞧,即使是垃圾收集器也有信息顯示出來——在這個示例中,是關于退出和堆的。

這些都只是表面上的——還有更多選項可用于調整。

事實上,這個改進并不能解決 一切問題 。因為 JEP 關注于通過它提供基礎設施和改變一些(很多?那一定是所有 GC 消息)已經存在的調用,而不是所有事情。雖然我找不到其它使用新機制的日志,但它可能就在那里。

命令行參數驗證

分享一件有趣的事情:Java 8 虛擬機要在使用到命令行參數的值時才會驗證它們。不幸的是,這在應用程序的生命周期中可能會顯得有些遲了,我可以預見隨著潛在的錯誤而來的崩潰。來看一個例子:

java -cp out-mr/mr.jar -XX:BlockLayoutMinDiamondPercentage=120 ...Main

我不知道 BlockLayoutMinDiamondPercentage 有什么作用(也懶得查),看起來 120不是一個有效的百分比。但 Java 8 沒有收到干擾,愉快地執行了 JAR 包中的代碼——顯然這并不是程序運行所需要的值。也許 120 就是一個有效的值呢?Java 9 并不這么看:

java9 -cp out-mr/mr.jar -XX:BlockLayoutMinDiamondPercentage=120 ...Main

intx BlockLayoutMinDiamondPercentage=120 is outside the allowed range [ 0 ... 100 ] Improperly specified VM option 'BlockLayoutMinDiamondPercentage=120' Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.</code></pre>

不錯 ... JEP 245 為 Java 9 帶來了在開始運行時驗證所有輸入參數的能力。從上面的例子可以看到,它也輸出了解釋性的錯誤消息。

 

來自:https://www.oschina.net/translate/inside-java-9-part-i

 

 本文由用戶 pijp9091 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!