使用追蹤代理程式收集中繼資料

Native Image 工具依賴於應用程式在執行階段可達程式碼的靜態分析。但是,分析無法始終完全預測 Java 原生介面 (JNI)、Java 反射、動態 Proxy 物件或類別路徑資源的所有用法。這些動態功能的未偵測到的用法必須以中繼資料(以程式碼預先計算或以 JSON 組態檔的形式)提供給 native-image 工具。

在這裡,您將找到有關如何自動收集應用程式的中繼資料並寫入 JSON 組態檔的資訊。若要瞭解如何在程式碼中計算動態功能呼叫,請參閱可達性中繼資料

目錄 #

追蹤代理程式 #

GraalVM 提供追蹤代理程式,可輕鬆收集中繼資料並準備組態檔。代理程式會追蹤一般 Java VM 上應用程式執行期間動態功能的所有用法。

使用 GraalVM JDK 中的 java 命令在命令列上啟用代理程式

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ ...

注意:-agentlib 必須在 -jar 選項或類別名稱或任何應用程式參數之前指定,作為 java 命令的一部分。

執行時,代理程式會尋找 native-image 工具需要額外資訊的類別、方法、欄位、資源。當應用程式完成且 JVM 結束時,代理程式會將中繼資料寫入指定輸出目錄 (/path/to/config-dir/) 中的 JSON 檔案。

可能需要多次執行應用程式 (使用不同的執行路徑),以改善動態功能的涵蓋範圍。config-merge-dir 選項會新增至現有的組態檔集,如下所示

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=/path/to/config-dir/ ...                                                              ^^^^^

代理程式還提供以下選項,可定期寫入中繼資料

  • config-write-period-secs=n:每隔 n 秒寫入中繼資料檔案;n 必須大於 0。
  • config-write-initial-delay-secs=n:等待 n 秒才首次寫入中繼資料;預設值為 1

例如

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/,config-write-period-secs=300,config-write-initial-delay-secs=5 ...

上述命令將在初始延遲 5 秒後,每 300 秒將中繼資料檔案寫入 /path/to/config-dir/

建議手動檢查產生的組態檔。由於代理程式僅觀察執行的程式碼,因此應用程式輸入應涵蓋盡可能多的程式碼路徑。

產生的組態檔可以透過將其放置在類別路徑上的 META-INF/native-image/ 目錄中,提供給 native-image 工具。此目錄(或其任何子目錄)會搜尋名為 reachability-metadata.json 的檔案,然後該檔案會自動包含在建置過程中。並非所有這些檔案都必須存在。當找到多個具有相同名稱的檔案時,會考慮所有檔案。

若要測試代理程式在範例應用程式上收集中繼資料,請前往使用反射建置原生可執行檔指南。

條件式中繼資料收集 #

代理程式可以根據執行程式碼中的用法推斷中繼資料條件。條件式中繼資料主要針對程式庫維護人員,目標是減少整體佔用空間。

若要使用代理程式收集條件式中繼資料,請參閱條件式中繼資料收集

代理程式進階用法 #

基於呼叫端的篩選器 #

依預設,代理程式會篩選 Native Image 在沒有組態的情況下支援的動態存取。篩選機制透過識別執行存取的 Java 方法(也稱為呼叫端方法),並將其宣告類別與一系列篩選規則比對來運作。內建篩選規則會排除源自 JVM 或 Native Image 直接支援的 Java 類別程式庫部分 (例如 java.nio) 中的動態存取,這些動態存取會從產生的組態檔中排除。正在存取的項目(類別、方法、欄位、資源等)與篩選無關。

除了內建篩選器外,還可以使用 caller-filter-file 選項指定具有其他規則的自訂篩選器檔案。例如:-agentlib:caller-filter-file=/path/to/filter-file,config-output-dir=...

篩選器檔案具有以下結構

{ "rules": [
    {"excludeClasses": "com.oracle.svm.**"},
    {"includeClasses": "com.oracle.svm.tutorial.*"},
    {"excludeClasses": "com.oracle.svm.tutorial.HostedHelper"}
  ],
  "regexRules": [
    {"includeClasses": ".*"},
    {"excludeClasses": ".*\\$\\$Generated[0-9]+"}
  ]
}

rules 區段包含一系列規則。每個規則都會指定 includeClasses,這表示來自比對類別的查詢將包含在產生的組態中,或指定 excludeClasses,這會將來自比對類別的查詢從組態中排除。每個規則都會定義一個模式以比對類別。模式可以以 .*.** 結尾,解譯如下:- .* 比對套件中的所有類別,而且只比對該套件;- .** 比對套件中的所有類別,以及任何深度的所有子套件。如果沒有 .*.**,則該規則僅適用於與模式比對的具有完整名稱的單一類別。所有規則都會依指定的順序處理,因此後續規則可以部分或完全覆寫先前的規則。當提供多個篩選器檔案時(透過指定多個 caller-filter-file 選項),它們的規則會依指定檔案的順序連結在一起。內建呼叫端篩選器的規則一律會先處理,因此可以在自訂篩選器檔案中覆寫這些規則。

在上述範例中,第一個規則會從產生的中繼資料中排除所有來自套件 com.oracle.svm 及其所有子套件(及其子套件等)的類別的查詢。但是,在下一個規則中,再次包含直接位於套件 com.oracle.svm.tutorial 中的那些類別的查詢。最後,再次排除來自 HostedHelper 類別的查詢。這些規則中的每一項都會部分覆寫先前的規則。例如,如果規則順序相反,則排除 com.oracle.svm.** 將是最後一個規則,並會覆寫所有其他規則。

regexRules 區段也包含一系列規則。其結構與 rules 區段的結構相同,但規則指定為正規表示式模式,這些模式會比對整個完整類別識別碼。regexRules 區段為選用。如果指定了 regexRules 區段,則當 (且僅當) rulesregexRules 都包含該類別,且兩者都未排除該類別時,才視為已包含該類別。如果沒有 regexRules 區段,則只有 rules 區段會決定是否包含或排除類別。

為了測試目的,可以透過新增 no-builtin-caller-filter 選項來停用 Java 類別程式庫查詢的內建篩選器,但產生的中繼資料檔案通常不適用於建置。同樣地,也可以使用 no-builtin-heuristic-filter 停用基於啟發式的 Java VM 內部存取的內建篩選器,而且通常也會導致較少可用的中繼資料檔案。例如:-agentlib:native-image-agent=no-builtin-caller-filter,no-builtin-heuristic-filter,config-output-dir=...

存取篩選器 #

與上述基於呼叫端的篩選器不同,基於呼叫端的篩選器會根據動態存取的來源位置篩選動態存取,存取篩選器適用於存取的目標。因此,存取篩選器可以直接排除產生的組態中的套件和類別(及其成員)。

依預設,所有存取的類別(也通過基於呼叫端的篩選器和內建篩選器)都包含在產生的組態中。使用 access-filter-file 選項,可以新增遵循上述檔案結構的自訂篩選器檔案。可以多次指定該選項以新增多個篩選器檔案,並且可以與其他篩選器選項結合使用,例如,-agentlib:access-filter-file=/path/to/access-filter-file,caller-filter-file=/path/to/caller-filter-file,config-output-dir=...

將組態檔指定為引數 #

可以使用 -H:ConfigurationFileDirectories=/path/to/config-dir/ 將包含不屬於類別路徑的組態檔的目錄指定給 native-image。此目錄必須直接包含 reachability-metadata.json 或先前使用的個別中繼資料檔案 (jni-config.jsonreflect-config.jsonproxy-config.jsonserialization-config.jsonresource-config.json)。可以使用 -H:ConfigurationResourceRoots=path/to/resources/ 提供位於類別路徑上但不在 META-INF/native-image/ 中的相同中繼資料檔案的目錄。-H:ConfigurationFileDirectories-H:ConfigurationResourceRoots 也都可以採用逗號分隔的目錄清單。

透過處理環境注入代理程式 #

如果 Java 程序是由應用程式或腳本檔案啟動,或者 Java 甚至嵌入在現有的程序中,要修改 java 命令列來注入代理程式可能會很困難。在這種情況下,也可以透過 JAVA_TOOL_OPTIONS 環境變數來注入代理程式。這個環境變數可能會被多個同時執行的 Java 程序所讀取,在這種情況下,每個代理程式必須使用 config-output-dir 寫入到不同的輸出目錄。(下一節將描述如何合併多組組態檔。)為了使用單一全域的 JAVA_TOOL_OPTIONS 變數來設定不同的路徑,代理程式的輸出路徑選項支援使用佔位符。

export JAVA_TOOL_OPTIONS="-agentlib:native-image-agent=config-output-dir=/path/to/config-output-dir-{pid}-{datetime}/"

{pid} 佔位符會被替換成程序識別碼,而 {datetime} 會被替換成系統日期和時間(以 UTC 為準),並按照 ISO 8601 格式化。以上述範例來說,產生的路徑可能是:/path/to/config-output-dir-31415-20181231T235950Z/

追蹤檔案 #

在上面的範例中,native-image-agent 同時用於追蹤 JVM 上的動態存取,並從中產生一組組態檔。然而,為了更好地了解執行情況,代理程式也可以寫入 JSON 格式的追蹤檔案,其中包含每次單獨的存取。

$JAVA_HOME/bin/java -agentlib:native-image-agent=trace-output=/path/to/trace-file.json ...

native-image-configure 工具可以將追蹤檔案轉換為組態檔。以下命令會讀取並處理 trace-file.json,並在 /path/to/config-dir/ 目錄中產生一組組態檔。

native-image-configure generate --trace-input=/path/to/trace-file.json --output-dir=/path/to/config-dir/

互通性 #

此代理程式使用 JVM 工具介面(JVMTI),並且可能與其他支援 JVMTI 的 JVM 一起使用。在這種情況下,必須提供代理程式的絕對路徑。

/path/to/some/java -agentpath:/path/to/graalvm/jre/lib/amd64/libnative-image-agent.so=<options> ...

實驗性選項 #

此代理程式有一些目前為實驗性的選項,這些選項可能會在未來的版本中啟用,但也可能會被更改或完全移除。請參閱 ExperimentalAgentOptions.md 指南。

Native Image 設定工具 #

如上一節所述,當同時在多個程序中使用代理程式時,config-output-dir 是一個安全的選項,但它會產生多組組態檔。可以使用 native-image-configure 工具來合併這些組態檔。

native-image-configure generate --input-dir=/path/to/config-dir-0/ --input-dir=/path/to/config-dir-1/ --output-dir=/path/to/merged-config-dir/

此命令會從 /path/to/config-dir-0/ 讀取一組組態檔,並從 /path/to/config-dir-1/ 讀取另一組組態檔,然後將包含它們所有資訊的一組組態檔寫入到 /path/to/merged-config-dir/。可以指定任意數量的 --input-dir 參數,其中包含組態檔。請參閱 native-image-configure help 以取得所有選項。

延伸閱讀 #

與我們聯繫