1. 程式人生 > >Java 9 揭祕(7. 建立自定義執行時映像)

Java 9 揭祕(7. 建立自定義執行時映像)

Tips
做一個終身學習的人。

Java 9
在第一章節中,主要介紹以下內容:

  • 什麼是自定義執行時映像和JIMAGE格式
  • 如何使用jlink工具建立自定義的執行時映像
  • 如何指定命令名稱來執行儲存在自定義映像中的應用程式
  • 如何使用jlink工具外掛

一. 什麼是自定義執行時映像?

在JDK 9之前,Java執行時映像可用作巨大整體的單體(artifact),從而增加了下載時間,啟動時間和記憶體佔用。單體JRE使得不可能在具有小記憶體的裝置上使用Java。 如果將Java應用程式部署到雲端,則需要支付使用的記憶體; 最常見的是,單體JRE使用的記憶體比所要求的記憶體還要多,因此為雲服務支付更多的記憶體。 在Java 8中引入的Compact配置檔案,以減少JRE大小,從而減少執行時記憶體佔用 —— 通過允許將JRE的一個子集打包在稱為Compact配置檔案的自定義執行時映像中。

Java 9採用了整體的方法來打包執行時映像。 所有平臺程式碼都已經模組化了。 你的應用程式程式碼也打包模組化了。 在Java 9中,可以建立一個自定義執行時,它將包含應用程式模組和應用程式所使用的平臺模組。 還可以在執行時映像中打包本地命令。 建立執行時映像的另一個好處是,你只需將一個包——執行時映像——傳送給你的應用程式使用者,而不需要下載並安裝單獨的JRE軟體包來執行應用程式。

執行時映像以特定格式儲存,稱為JIMAGE,該格式針對空間和速度進行了優化。 僅在執行時支援JIMAGE格式。 它是用於在JDK中儲存和索引模組,類和資源的容器格式。 從JIMAGE檔案搜尋和載入類比從JAR和JMOD檔案快很多。 JIMAGE格式是JDK內部的,開發人員很少需要直接與JIMAGE檔案進行互動。

預計JIMAGE格式將隨著時間的推移而不斷髮展,因此其內部部件不會面向開發人員。 JDK 9附帶了一個名為jimage的工具,可用於瀏覽JIMAGE檔案。

Tips
可以使用jlink工具來建立一個執行時映像,它使用一種名為JIMAGE的新檔案來儲存模組。 JDK 9附帶jimage工具,可以瀏覽JIMAGE檔案的內容。

如果你的程式碼期望將執行時映像儲存在名為rt.jar檔案的檔案中,請謹慎。 JDK執行庫儲存在JDK 9之前的rt.jar檔案中,但在JDK 9中不再是這樣。當將應用程式遷移到JDK 9時,可能會破壞你的程式碼。

二. 建立自定義執行時映像

可以使用jlink工具建立特定於平臺的執行時映像。 執行時映像將包含指定的應用程式模組和只需的平臺模組,從而減少執行時映像的大小。 這對於在具有少量記憶體的嵌入式裝置上執行的應用程式非常有用。 JDK 9附帶了jlink工具。 它位於JDK_HOME\bin目錄中。 執行jlink工具的一般語法如下:

jlink <options> --module-path <modulepath> --add-modules <mods> --output <path>

在這裡,<options>包括jlink的零個或多個選項,如下面表格所示,<modulepath>是平臺和應用程式模組所在的模組路徑以新增到映像中。 模組可以是模組化的JAR,展開目錄和JMOD檔案。 <mods>是要新增到映像的模組的列表,這可能會導致新增其他模組,因為其他模組的傳遞依賴關係。 <path>是生成的執行時映像被儲存的輸出目錄。

選項 描述
--add-modules <mod>,<mod>... 指定要解析的根模組列表。 所有已解析的模組將被新增到執行時映像中。
--bind-services 在連結過程中執行完整的服務繫結。 如果新增的模組包含uses語句,jlink將掃描模組路徑上的所有JMOD檔案,包括在uses語句中指定的服務執行時映像中的所有服務提供者的模組。
-c, --compress <0 OR 1 OR 2>[:filter=<pattern-list>] 指定輸映像中所有資源的壓縮級別。 0表示常量字串共享,1表示ZIP,2表示兩者。 可以指定可選的<pattern-list>過濾列出要包括的檔案的模式。
--disable-plugin <plugin-name> 禁用指定的外掛。
--endian <little OR big> 指定生成的執行時映像的位元組指令。 預設值是本地平臺的位元組指令。
-h,--help 列印使用說明和jlink工具的所有選項列表。
--ignore-signing-information 當簽名的模組化JAR連結在映像中時,抑制致命錯誤。 與簽名的模組化JAR的相關的簽名檔案不會複製到執行時映像。
--launcher <command>=<module> 指定模組的啟動器命令。 <command>是要生成以啟動應用程式的命令的名稱,例如runmyapp。 該工具將建立一個指令碼或批處理檔案,<command>以執行<module>中的主類。
--launcher <command>=<module>/<main-class> 指定模組和主類的啟動器命令。 <command>是要生成以啟動應用程式的命令的名稱,例如runmyapp。 該工具將建立一個指令碼/批處理檔案,<command>以執行<module>中的<main-class>
--limit-modules <mod>,<mod> 將可觀察模組限制在命名模組的傳遞性關閉主模組(如果指定)以及使用--add-modules選項指定的任何其他模組中。
--list-plugins 列出可用的外掛。
-p, --module-path <modulepath> 指定找到將平臺和應用程式模組新增到執行時映像的模組路徑。
--no-header-files 排除原生代碼的include標頭檔案。
--no-man-pages 排除手冊主頁。
--output <path> 指定要複製執行時映像的目錄。
--save-opts <filename> 將jlink選項儲存在指定的檔案中。
-G, --strip-debug 從輸出映像中查詢除錯資訊。
--suggest-providers [<service-name>,...] 如果沒有指定服務名稱,它會建議將為新增的模組連結的所有服務的提供程式的名稱。如果指定一個或多個服務名稱,它會建議指定服務名稱的提供者。 在建立映像之前,可以使用此選項,以瞭解在使用--bind-services選項時將包括哪些服務。
-v, --verbose 列印詳細輸出。
--version 列印jlink工具的版本。
@<filename> 從指定的檔案讀取選項。

讓我們建立一個執行時映像,其中包含素數檢查應用程式和所需平臺模組的四個模組,其中僅包含java.base模組。 請注意,以下命令僅包含素數檢查程式應用程式中的三個模組。 第四個將被新增,因為這三個依賴於第四個模組。 命令後面的文字詳細解釋了這一點。

C:\Java9Revealed>jlink --module-path jmods;C:\java9\jmods
  --add-modules com.jdojo.prime.client,com.jdojo.prime.generic,com.jdojo.prime.faster
  --launcher runprimechecker=com.jdojo.prime.client
  --output primechecker

在解釋此命令的所有選項之前,讓我們驗證執行時映像是否已成功建立。 該命令應該將執行時映像複製到C: Java9Revealed\primechecker資料夾。 執行以下命令以驗證執行時映像包含五個模組:

C:\Java9Revealed>primechecker\bin\java --list-modules

輸出結果為:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]ea

如果你獲得的輸出類似於此處所示,執行時映像已正確建立。 在輸出中的@符號之後顯示的模組版本號可能與你的有所不同。

--module-path選項指定兩個目錄,jmods和C: java9\jmods。 在C: Java9Revealed\jmods目錄中儲存了素數檢查程式的四個JMOD檔案。 模組路徑中的第一個元素允許jlink工具查詢所有應用程式模組。 將JDK 9安裝在C:\java9目錄下,所以模組路徑中的第二個元素讓工具找到平臺模組。 如果沒有指定第二部分,則會出現錯誤:

Module java.base not found.

--add-modules選項指定素數檢查程式應用程式的三個模組。 可能會想知道為什麼我們沒有使用此選項指定第四個模組com.jdojo.prime。 此列表包含根模組,而不僅僅包含在執行時映像中的模組。 jlink工具將解決所有這些根模組的依賴關係,並將所有已解析的依賴模組包含在執行時映像中。 這三個模組取決於com.jdojo.prime模組,它將通過將其定位在模組路徑中來解析,因此將被包含在執行時映像中。 該映像還將包含java.base模組,因為所有應用程式模組都隱含依賴於它。

--output選項指定執行時映像將被複制的目錄。 該命令將執行時映像複製到C:\Java9Revealed\primechecker目錄。 輸出目錄包含以下子目錄和名為release的檔案:

  • bin
  • conf
  • include
  • legal
  • lib

bin目錄包含可執行檔案。 在Windows上,它還包含動態連結的本地類庫(.dll檔案)。
conf目錄包含可編輯的配置檔案,如.properties和.policy檔案。
include目錄包含C / C ++標頭檔案。
legal目錄包含法律宣告。
lib目錄包含新增到執行時映像的模組,以及其他檔案。 在Mac,Linux和Solaris上,它還將包含系統的動態連結本地類庫。

使用--launcher選項與jlink命令。 指定了命令名稱runprimechecker,模組名稱為com.jdojo.prime.client--launcher選項使jlink在bin目錄中的Windows上建立一個平臺特定的可執行檔案,例如runprimechecker.bat檔案。 可以使用此可執行檔案來執行你的應用程式。 檔案內容只是在這個模組中執行主類的包裝器。 可以使用此檔案來執行應用程式:

C:\Java9Revealed> primechecker\bin\runprimechecker

輸出結果為:

Using jdojo.faster.primechecker:
3 is a prime.
4 is not a prime.
121 is not a prime.
977 is a prime.
Using jdojo.faster.primechecker:
3 is a prime.
4 is not a prime.
121 is not a prime.
977 is a prime.
A PrimeChecker service provider with the name 'jdojo.probable.primechecker' was not found.

還可以使用java命令來啟動應用程式,使用jlink工具已經將檔案複製到bin目錄下:

C:\Java9Revealed>primechecker\bin\java --module com.jdojo.prime.client

此命令的輸出將與上一個命令的輸出相同。 請注意,不必指定模組路徑。 連結器jlink工具在建立執行時映像時處理模組路徑。 當執行生成的執行時映像的java命令時,它會知道在哪裡找到模組。 還要注意,不必為命令指定主類名稱。 剛才指定了模組名稱。 已經設定了com.jdojo.prime.client模組的main-class屬性。 當執行模組而不指定主類時,該模組的module-info.class檔案中設定的main-class屬性將用作主類。

三. 繫結服務

在上一節中,為素數服務客戶端應用程式建立了執行時映像。 必須使用要包含在映像中的--add-modules選項指定所有服務提供者模組的名稱。 在本節中,將展示如何在使用jlink工具使用--bind-services選項建立執行時映像時自動繫結服務。 這一次,需要將模組(即com.jdojo.prime模組)新增到模組圖中,並且jlink工具將負責其餘部分。 com.jdojo.prime.client模組讀取com.jdojo.prime模組,因此將前者新增到模組圖中也將解決後者。 以下命令列印執行時映像的建議服務提供程式列表。

C:\Java9Revealed>jlink --module-path jmods;C:\java9\jmods
--add-modules com.jdojo.prime.client
--suggest-providers

以下是部分輸出內容:

module com.jdojo.prime located (file:///C:/Java9Revealed/jmods/com.jdojo.prime.jmod)
    uses com.jdojo.prime.PrimeChecker
module com.jdojo.prime.client located (file:///C:/Java9Revealed/jmods/com.jdojo.prime.client.jmod)
module java.base located (file:///C:/java9/jmods/java.base.jmod)
    uses java.lang.System$LoggerFinder
    uses java.net.ContentHandlerFactory
...
Suggested providers:
  module com.jdojo.prime.faster provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime
  module com.jdojo.prime.generic provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime
  module com.jdojo.prime.probable provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime
  module java.desktop provides java.net.ContentHandlerFactory, used by java.base
 ...

該命令僅將com.jdojo.prime.client模組指定給--add-modules選項。 com.jdojo.primejava.base模組被解析,因為com.jdojo.prime.client模組讀取它們。 掃描所有已解析的模組的uses語句,隨後掃描模組路徑中的所有模組,以使用在uses語句中指定的服務的服務提供者。 所有找到的服務提供者都被打印出來。

Tips
可以為--suggest-providers選項指定引數。 如果沒有引數使用它,請確保在命令結束時指定它。 否則,--suggest-providers選項之後的選項將被解釋為其引數,將收到錯誤。

以下命令將com.jdojo.prime.PrimeChecker指定為--suggest-providers選項的服務名稱,以列印為此服務找到的所有服務提供者:

C:\Java9Revealed>jlink --module-path jmods;C:\java9\jmods
--add-modules com.jdojo.prime.client
--suggest-providers com.jdojo.prime.PrimeChecker

輸出結果:

Suggested providers:
  module com.jdojo.prime.faster provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime
  module com.jdojo.prime.generic provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime
  module com.jdojo.prime.probable provides com.jdojo.prime.PrimeChecker, used by com.jdojo.prime

使用與前述相同的邏輯,找到所有三個服務提供者。 讓我們建立一個包含所有三個服務提供者的新的執行時映像。 以下命令執行該操作:

C:\Java9Revealed>jlink --module-path jmods;C:\java9\jmods
--add-modules com.jdojo.prime.client
--launcher runprimechecker=com.jdojo.prime.client
--bind-services
--output primecheckerservice

將此命令與上一節中使用的命令進行比較。 這次,只使用--add-modules選項指定了一個模組。 也就是說,不必指定服務提供者模組的名稱。 使用了--bind-services選項,因此新增的模組中的所有服務提供者引用都將自動新增到執行時映像。 指定了一個名為primecheckerservice的新輸出目錄。 以下命令執行新建立的執行時映像:

C:\Java9Revealed>primecheckerservice\bin\runprimechecker

以下是輸出結果:

Using jdojo.generic.primechecker:
3 is a prime.
4 is not a prime.
121 is not a prime.
977 is a prime.
Using jdojo.faster.primechecker:
3 is a prime.
4 is not a prime.
121 is not a prime.
977 is a prime.
Using jdojo.probable.primechecker:
3 is a prime.
4 is not a prime.
121 is not a prime.
977 is a prime.

輸出證明,模組路徑中的所有三個素數檢查服務提供者都自動新增到執行時映像中。

四. 使用jlink工具外掛

jlink工具使用外掛架構來建立執行時映像。 它將所有類,本地類庫和配置檔案收集到一組資源中。 它構建了一個轉換器管道,它們是指定為命令列選項的外掛。 資源進入管道。 管道中的每個轉換器對資源進行某種變換,並將變換的資源輸送到下一個轉換器。 最後,jlink將轉換的資源提供給映像構建器。

JDK 9為jlink工具附帶了幾個外掛。 這些外掛定義了命令列選項。 要使用外掛,需要使用命令列選項。 可以使用--list-plugins選項執行jlink工具,使用其描述和命令列選項列印所有可用外掛的列表:

C:\Java9Revealed>jlink --list-plugins

以下是輸出結果:

List of available plugins:
Plugin Name: class-for-name
Option: --class-for-name
Description: Class optimization: convert Class.forName calls to constant loads.
Plugin Name: compress
Option: --compress=<0|1|2>[:filter=<pattern-list>]
Description: Compress all resources in the output image.
Level 0: constant string sharing
Level 1: ZIP
Level 2: both.
An optional <pattern-list> filter can be specified to list the pattern of
files to be included.
Plugin Name: dedup-legal-notices
Option: --dedup-legal-notices=[error-if-not-same-content]
Description: De-duplicate all legal notices.  If error-if-not-same-content is
specified then it will be an error if two files of the same filename
are different.
Plugin Name: exclude-files
Option: --exclude-files=<pattern-list> of files to exclude
Description: Specify files to exclude. e.g.: **.java,glob:/java.base/native/client/**
Plugin Name: exclude-jmod-section
Option: --exclude-jmod-section=<section-name>
where <section-name> is "man" or "headers".
Description: Specify a JMOD section to exclude
Plugin Name: exclude-resources
Option: --exclude-resources=<pattern-list> resources to exclude
Description: Specify resources to exclude. e.g.: **.jcov,glob:**/META-INF/**
Plugin Name: generate-jli-classes
Option: [email protected]
Description: Takes a file hinting to jlink what java.lang.invoke classes to pre-generate. If
this flag is not specified a default set of classes will be generated.
Plugin Name: include-locales
Option: --include-locales=<langtag>[,<langtag>]*
Description: BCP 47 language tags separated by a comma, allowing locale matching
defined in RFC 4647. e.g.: en,ja,*-IN
Plugin Name: order-resources
Option: --order-resources=<pattern-list> of paths in priority order.  If a @file
is specified, then each line should be an exact match for the path to be ordered
Description: Order resources. e.g.: **/module-info.class,@classlist,/java.base/java/lang/**
Plugin Name: release-info
Option: --release-info=<file>|add:<key1>=<value1>:<key2>=<value2>:...|del:<key list>
Description: <file> option is to load release properties from the supplied file.
add: is to add properties to the release file.
Any number of <key>=<value> pairs can be passed.
del: is to delete the list of keys in release file.
Plugin Name: strip-debug
Option: --strip-debug
Description: Strip debug information from the output image
Plugin Name: strip-native-commands
Option: --strip-native-commands
Description: Exclude native commands (such as java/java.exe) from the image
Plugin Name: system-modules
Option: --system-modules
Description: Fast loading of module descriptors (always enabled)
Plugin Name: vm
Option: --vm=<client|server|minimal|all>
Description: Select the HotSpot VM in the output image.  Default is all
For options requiring a <pattern-list>, the value will be a comma separated
list of elements each using one the following forms:
  <glob-pattern>
  glob:<glob-pattern>
  regex:<regex-pattern>
  @<filename> where filename is the name of a file containing patterns to be
              used, one pattern per line

以下命令使用compressstrip-debug外掛。 壓縮外掛將壓縮映像,這將得到較小的映像。 這裡使用壓縮級別2來進行最大壓縮。 strip-debug外掛從Java程式碼中刪除除錯資訊,從而進一步減小映像的大小。 在執行此命令之前,請確保刪除先前建立的primechecker目錄。

C:\Java9Revealed>jlink --module-path jmods;C:\java9\jmods
  --compress 2
  --strip-debug
  --add-modules com.jdojo.prime.client,com.jdojo.prime.generic,com.jdojo.prime.faster
  --launcher runprimechecker=com.jdojo.prime.client
  --output primechecker

Tips
目前外掛API是完全實驗性的,並且未定義外掛的執行順序。 在早期的實現中,jlink工具還支援定製外掛,後來被刪除。

五. jimage 工具

Java執行時在JIMAGE檔案中運送模組執行時映像。 該檔名為modules,它位於JAVA_HOME\lib中,其中JAVA_HOME可以是JDK_HOME或JRE_HOME。 jimage工具用於瀏覽JIMAGE檔案的內容。 它可以:

  • 從JIMAGE檔案中提取條目
  • 列印儲存在JIMAGE中的內容的摘要
  • 列印其名稱,大小,偏移量等條目列表。
  • 驗證類檔案

jimage工具儲存在JDK_HOME\bin目錄中。 命令的一般格式如下:

jimage <subcommand> <options> <jimage-file-list>

這裡,<subcommand>是下面第一個表格列出的子命令之一。 <options>是第二個表格列出的一個或多個選項;<jimage-file-list>是一個空格分隔的JIMAGE檔案列表。

子命令 描述
extract 從指定的JIMAGE檔案中將所有條目解壓縮到當前目錄。 使用--dir選項為提取的條目指定另一個目錄。
info 列印包含在指定JIMAGE檔案頭部的詳細資訊。
list 在指定的JIMAGE檔案中列印所有模組及其條目的列表。 使用--verbose選項包括條目的詳細資訊,例如其大小,偏移量以及條目是否被壓縮。
verify 在指定的JIMAGE檔案中列印驗證不是類的.class條目列表。
選項 描述
-dir <dir-name> 指定提取子命令的目標目錄,其中將提取JIMAGE檔案中的條目。
-h, --help 列印jimage工具的使用資訊。
--include <pattern-list> 指定過濾條目的模式列表。 模式列表的值是以逗號分隔的元素列表,每個元素使用以下形式之一:<glob-pattern>glob:<glob-pattern>regex:<regex-pattern>
--full-version 列印jimage工具的完整版本資訊。
--verbose 當與列表子命令一起使用時,列印詳細資訊,如大小,偏移量和壓縮級別。
--version 列印jimage工具的版本資訊。

舉幾個使用jimage命令的例子。 示例使用儲存在C:\java9\lib\modules上的JDK 9執行時映像。 當執行這些示例時,將需要將其替換為你的映像位置。 還可以使用這些示例中由jlink工具建立的任何自定義執行時映像。

以下命令從執行時映像中提取所有條目,並將其複製到extracted_jdk目錄。 該命令需要幾秒鐘才能完成。

C:\Java9Revealed>jimage extract --dir extracted_jdk C:\java9\lib\modules

以下命令將以.png副檔名的所有影象條目從JDK執行時映像提取到extracted_images目錄中:

C:\Java9Revealed>jimage extract --include regex:.+\.png --dir extracted_images C:\java9\lib\modules

以下命令列出執行時映像中的所有條目。 顯示部分輸出:

C:\Java9Revealed>jimage list C:\java9\lib\modules

以下是部分輸出內容:

jimage: C:\java9\lib\modules
Module: java.activation
    META-INF/mailcap.default
    META-INF/mimetypes.default
...
Module: java.annotations.common
    javax/annotation/Generated.class
...

以下命令列出執行時映像中的所有條目以及條目的詳細資訊。 請注意使用--verbose選項。

C:\Java9Revealed>jimage list --verbose C:\java9\lib\modules

以下是部分輸出:

jimage: C:\java9\lib\modules
Module: java.activation
Offset     Size   Compressed Entry
34214466    292            0 META-INF/mailcap.default
34214758    562            0 META-INF/mimetypes.default
...
Module: java.annotations.common
Offset     Size   Compressed Entry
34296622    678            0 javax/annotation/Generated.class
...

以下命令列印無效的類檔案列表。 你可能會想知道你如何使類檔案無效。 通常,你不會有一個無效的類檔案 —— 但黑客會! 但是,要執行此示例,需要一個無效的類檔案。 可以使用一個簡單的想法 —— 拿一個有效的類檔案,在文字編輯器中開啟它,並部分和隨機地刪除其內容,使其成為無效的類檔案。 將編譯的類檔案的內容複製到Main2.class檔案中,並刪除了其中的一些內容,使其成為無效的類。 將Main2.class檔案新增到與Main.class相同目錄中的com.jdojo.prime.client模組中。 使用上一個命令為此示例的素數檢查應用程式重新建立了執行時映像。 如果使用JDK附帶的Java執行時映像,則不會看到任何輸出,因為JDK執行時映像中的所有類檔案都有效。

C:\Java9Revealed>jimage verify primechecker\lib\modules

會得到以下錯誤資訊:

jimage: primechecker\lib\modules
Error(s) in Class: /com.jdojo.prime.client/com/jdojo/prime/client/Main2.class

六. 總結

在JDK 9中,執行時映像以特定格式儲存,稱為JIMAGE,該格式針對空間和速度進行了優化。 僅在執行時支援JIMAGE格式。 它是用於在JDK中儲存和索引模組,類和資源的容器格式。 從JIMAGE檔案搜尋和載入類比從JAR和JMOD檔案快很多。 JIMAGE格式是JDK內部的,開發人員很少需要直接與JIMAGE檔案進行互動。

它附帶了一個名為jlink的工具,可為應用程式建立一個JIMAGE格式的執行時映像,該應用程式將包含應用程式模組和應用程式所使用的那些平臺模組。 jlink工具可以從儲存在模組JAR,展開的目錄和JMOD檔案中的模組建立執行時映像。 JDK 9附帶了一個名為jimage的工具,可用於瀏覽JIMAGE檔案的內容。