tl;dr
GraalVM 21.0.1でJavaのバイナリを生成して、バイナリにSBOMを埋めこんで取り出してみた。
vim HelloWorld.java
javac HelloWorld.java
native-image HelloWorld --enable-sbom
native-image-inspect helloworld --sbom | jq .
概要
Java言語は通常、JDKなどを使用して、jarファイルを生成して、JREを使って実行します。しかし、GraalVMというJDKを使うと、ネイティブビルド(実行ファイル)を生成できます。そのため、実行時に、JREなどが不要になります。また実行ファイルにSBOMを埋め込むことができます。埋めこまれたSBOMの情報を取りだすこともできます。本記事でこれらの技術を試しています。
環境
実行した環境は次のとおりです。
- macOS sonoma 14.1.1 (intel mac)
- file コマンド
- jq コマンド
- Oracle GraalVM 21.0.1+12.1
file
コマンドは macOSに付属のものを使用しています。jq
コマンドは homebrew
由来のものを使用しています。必要であればインストールしてください。
GraalVMのバージョン
2023年11月15日現在、Graal VMは 17系と21系が配布されています。本記事で21系を選択しています。
二種類のGraalVM
GraalVM はライセンスの異なる二つの GraalVM があるので注意してください。
- Oracle GraalVM
- License : GraalVM Free Terms and Conditions
- ダウンロードページ
- GraalVM Community Edition
- License : GPL-2.0 with Classpath exception
- ダウンロードページ
Oracle GraalVMとGraalVM Community Editionはほぼ同じですが、Polyglot Sandboxing 機能と、Managed Execution機能はOracle GraalVMにしかありません。詳細については、こちらを参照してください。また、ライセンスが異なります。インストールする前にライセンスをよく比較して選択してください。なお、GraalVM Community Edition のライセンスは、今までのOpenJDKと同じライセンスです。これらのライセンスについては、こちらに説明があります。
今回は、ただのおためしで、成果物の配布も行う予定もなかったので、Oracle GraalVMを選択したが、利益を産むような商用利用は契約が必要かもしれません。
これらのGraalVMのインストール方法を参照してください。
GraalVM JDKの選択
GraalVM は JDK ですので、通常どおり java_home
コマンドで表示・選択できます。インストール済みのJDKと選択されているJDKを表示するコマンドの実行例です。
/usr/libexec/java_home -V
実行結果の例です。実際にインストールされているJDKによって表示内容は異なります。
Matching Java Virtual Machines (5):
21.0.1 (x86_64) "Oracle Corporation" - "Oracle GraalVM 21.0.1+12.1" /Library/Java/JavaVirtualMachines/graalvm-jdk-21.0.1+12.1/Contents/Home
21.0.1 (x86_64) "Eclipse Adoptium" - "OpenJDK 21.0.1" /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
17.0.9 (x86_64) "Eclipse Adoptium" - "OpenJDK 17.0.9" /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home
11.0.21 (x86_64) "Eclipse Adoptium" - "OpenJDK 11.0.21" /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
1.8.0_392 (x86_64) "Eclipse Temurin" - "Eclipse Temurin 8" /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/graalvm-jdk-21.0.1+12.1/Contents/Home
また、このJAVA_HOMEの下のbinディレクトリを環境変数 PATH に追加されていることを確認してください。
参考までに現在のJAVA_HOMEディレクトリ直下のbinディレクトリをPATHの先頭に追加するには次のコマンドを実行します。
export PATH=$(/usr/libexec/java_home)/bin:$PATH
さらに、javacコマンドのバージョンは次のコマンドで確認できます。説明どおりであれば、javac 21.0.1
と出力されるはずです。
javac -version
サンプルJavaのコード
今回は実行するJavaのコードは、よくある Hello World!
を表示する簡単なコードです。任意のエディタやIDEで次の内容のHelloWorld.java
ファイルを作成してください。
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
GraalVMのjavacでコンパイル
通常 Javaのビルドには gradle
や mvn
コマンドを使います。しかし、ここでは簡単のために、javacコマンドで、HelloWorld.java
Java ソースファイルから HelloWorld.class
Java クラスファイルを生成します。次のコマンドを実行すると、HelloWorld.java
のあるディレクトリにHelloWorld.class
が生成されます。
javac HelloWorld.java
ファイルの確認
ソースコードとクラスファイルの内容を file
コマンドで確認します。
file HelloWorld.*
実行結果の例です。 HelloWorld.class
が生成されており、Java のクラスファイルであることが確認できました。
HelloWorld.class: compiled Java class data, version 65.0
HelloWorld.java: Java source text, ASCII text
ネィティブイメージの生成(ネイティブバイナリの生成)
GraalVMのnative-image
コマンドを使って Java クラスファイルから ネイティブイメージを生成します。Javaクラスファイルには HelloWorld
クラスがありますので、クラス名を指定して実行します。
このコマンドはHelloWorld.class
ファイルと同じディレクトリで実行してください。
native-image HelloWorld
実行結果の例です。
================================================================================
GraalVM Native Image: Generating 'helloworld' (executable)...
================================================================================
[1/8] Initializing... (16.1s @ 0.07GB)
Java version: 21.0.1+12, vendor version: Oracle GraalVM 21.0.1+12.1
Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: off
C compiler: cc (apple, x86_64, 15.0.0)
Garbage collector: Serial GC (max heap size: 80% of RAM)
1 user-specific feature(s):
- com.oracle.svm.thirdparty.gson.GsonFeature
--------------------------------------------------------------------------------
Build resources:
- 12.09GB of memory (75.6% of 16.00GB system memory, determined at start)
- 8 thread(s) (100.0% of 8 available processor(s), determined at start)
[2/8] Performing analysis... [*****] (13.9s @ 0.18GB)
2,111 reachable types (60.8% of 3,471 total)
2,002 reachable fields (45.6% of 4,392 total)
9,600 reachable methods (38.1% of 25,170 total)
754 types, 109 fields, and 475 methods registered for reflection
49 types, 33 fields, and 48 methods registered for JNI access
4 native libraries: -framework Foundation, dl, pthread, z
[3/8] Building universe... (2.3s @ 0.21GB)
[4/8] Parsing methods... [*] (1.6s @ 0.25GB)
[5/8] Inlining methods... [***] (1.4s @ 0.26GB)
[6/8] Compiling methods... [*****] (27.2s @ 0.28GB)
[7/8] Layouting methods... [*] (1.9s @ 0.23GB)
[8/8] Creating image... [**] (2.4s @ 0.29GB)
3.19MB (45.18%) for code area: 4,519 compilation units
3.74MB (52.92%) for image heap: 57,264 objects and 43 resources
137.56kB ( 1.90%) for other data
7.06MB in total
--------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
1.72MB java.base 837.42kB byte[] for code metadata
1.24MB svm.jar (Native Image) 720.85kB byte[] for java.lang.String
85.61kB com.oracle.svm.svm_enterprise 436.26kB heap alignment
25.54kB org.graalvm.nativeimage.base 382.90kB java.lang.String
21.67kB org.graalvm.collections 334.62kB java.lang.Class
20.21kB jdk.internal.vm.ci 157.69kB java.util.HashMap$Node
18.80kB jdk.proxy3 114.01kB char[]
17.00kB jdk.proxy1 99.64kB byte[] for reflection metadata
13.10kB jdk.internal.vm.compiler 88.66kB java.lang.Object[]
7.27kB jdk.proxy2 82.46kB c.o.s.c.h.DynamicHubCompanion
389.00B for 1 more packages 573.49kB for 562 more object types
Use '-H:+BuildReport' to create a report with more details.
--------------------------------------------------------------------------------
Security report:
- Binary does not include Java deserialization.
- Use '--enable-sbom' to embed a Software Bill of Materials (SBOM) in the binary.
--------------------------------------------------------------------------------
Recommendations:
PGO: Use Profile-Guided Optimizations ('--pgo') for improved throughput.
INIT: Adopt '--strict-image-heap' to prepare for the next GraalVM release.
HEAP: Set max heap for improved and more predictable memory usage.
CPU: Enable more CPU features with '-march=native' for improved performance.
QBM: Use the quick build mode ('-Ob') to speed up builds during development.
--------------------------------------------------------------------------------
2.7s (3.8% of total time) in 303 GCs | Peak RSS: 0.73GB | CPU load: 3.72
--------------------------------------------------------------------------------
Produced artifacts:
/Users/fu7mu4/Documents/hello-graalvm/helloworld (executable)
================================================================================
Finished generating 'helloworld' in 1m 7s.
確認
同じように file
コマンドでネィティブの実行ファイルが生成されているか確認します。
file helloworld
実行結果は次のとおりです。ただし、intel macでの実行結果のため M1, M2, M3 macでは異なる結果になるでしょう。
helloworld: Mach-O 64-bit executable x86_64
実行可能なファイル形式であることが確認できたので実行します。
./helloworld
実行結果は次のとおりです。確かに Hello World!
と表示されました。
Hello World!
native-imageコマンドはどこから?
native-image
コマンドは GraalVM v21に付属していました。v17系の場合は gu
コマンド(graalvm updater)を使って別途インストールが必要かもしれません。詳細はInstall Native Imageを確認してください。
SBOMの埋め込み
先程のネイティブバイナリを生成したときにSoftware Bill of Materials
(SBOM)を埋めこみについて書かれていました。
--------------------------------------------------------------------------------
Security report:
- Binary does not include Java deserialization.
- Use '--enable-sbom' to embed a Software Bill of Materials (SBOM) in the binary.
--------------------------------------------------------------------------------
SBOMとは、ソフトウェアの部品表のことです。ここでは、ネイティブの実行ファイルにどんなコンポーネント(ライブラリなど)が含まれているかを示す情報のことです。SBOMがあれば、そのバイナリにどのようなライブラリが使用されているかがわかるので、例えば、含まれているライブラリに既知の脆弱性があるかどうかなどの調査ができるようになります。
SBOMのよく知られたフォーマットには、SPDXとCychloneDXがあります。GrralVMのサポートしている SBOM は CychloneDX形式だでです。
GraalVM のSBOM埋め込みの説明は こちら にあります。
SBOM 埋め込みのネイティブバイナリの生成
SBOMを埋めこんだネイティブバイナリを生成します。先程のコマンドに、--enable-sbom
を追加するだけで生成できます。ネイティブバイナリを生成する際に、GraalVMがどのようなライブラリをリンクするかなどを調査して自動的にSBOMを埋めこむようです。
次のようなコマンドを実行します。
native-image HelloWorld --enable-sbom
次のように標準出力がでて、hellowworld
ファイルが生成されます。同名のファイルがあった場合は上書きされす。
================================================================================
GraalVM Native Image: Generating 'helloworld' (executable)...
================================================================================
[1/8] Initializing... (11.1s @ 0.07GB)
Java version: 21.0.1+12, vendor version: Oracle GraalVM 21.0.1+12.1
Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: off
C compiler: cc (apple, x86_64, 15.0.0)
Garbage collector: Serial GC (max heap size: 80% of RAM)
1 user-specific feature(s):
- com.oracle.svm.thirdparty.gson.GsonFeature
--------------------------------------------------------------------------------
Build resources:
- 12.09GB of memory (75.6% of 16.00GB system memory, determined at start)
- 8 thread(s) (100.0% of 8 available processor(s), determined at start)
Warning: We could not find any version info for the following classes:
HelloWorld
[2/8] Performing analysis... [*****] (10.4s @ 0.19GB)
2,111 reachable types (60.8% of 3,471 total)
2,002 reachable fields (45.6% of 4,392 total)
9,600 reachable methods (38.2% of 25,159 total)
754 types, 109 fields, and 475 methods registered for reflection
49 types, 33 fields, and 48 methods registered for JNI access
4 native libraries: -framework Foundation, dl, pthread, z
[3/8] Building universe... (1.8s @ 0.21GB)
[4/8] Parsing methods... [*] (1.4s @ 0.25GB)
[5/8] Inlining methods... [***] (1.3s @ 0.25GB)
[6/8] Compiling methods... [*****] (25.0s @ 0.33GB)
[7/8] Layouting methods... [*] (1.3s @ 0.31GB)
[8/8] Creating image... [**] (2.3s @ 0.27GB)
3.20MB (45.16%) for code area: 4,519 compilation units
3.74MB (52.86%) for image heap: 57,283 objects and 43 resources
143.88kB ( 1.98%) for other data
7.08MB in total
--------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
1.73MB java.base 838.19kB byte[] for code metadata
1.24MB svm.jar (Native Image) 720.85kB byte[] for java.lang.String
85.61kB com.oracle.svm.svm_enterprise 439.08kB heap alignment
25.54kB org.graalvm.nativeimage.base 382.90kB java.lang.String
21.67kB org.graalvm.collections 334.62kB java.lang.Class
20.21kB jdk.internal.vm.ci 157.69kB java.util.HashMap$Node
18.80kB jdk.proxy3 114.01kB char[]
17.00kB jdk.proxy1 99.64kB byte[] for reflection metadata
13.10kB jdk.internal.vm.compiler 88.75kB java.lang.Object[]
7.27kB jdk.proxy2 82.46kB c.o.s.c.h.DynamicHubCompanion
389.00B for 1 more packages 573.82kB for 562 more object types
Use '-H:+BuildReport' to create a report with more details.
GraalVM Native Image (https://www.graalvm.org/native-image/)
This tool can ahead-of-time compile Java code to native executables.
Usage: native-image [options] class [imagename] [options]
(to build an image for a class)
or native-image [options] -jar jarfile [imagename] [options]
(to build an image for a jar file)
or native-image [options] -m <module>[/<mainclass>] [options]
native-image [options] --module <module>[/<mainclass>] [options]
(to build an image for a module)
where options include:
@argument files one or more argument files containing options
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
--class-path <class search path of directories and zip/jar files>
A : separated list of directories, JAR archives,
and ZIP archives to search for class files.
-p <module path>
--module-path <module path>...
A : separated list of directories, each directory
...skipping...
--------------------------------------------------------------------------------
Security report:
- Binary does not include Java deserialization.
- Embedded SBOM contains 4 component(s) and is 325.00B in size.
--------------------------------------------------------------------------------
Recommendations:
PGO: Use Profile-Guided Optimizations ('--pgo') for improved throughput.
INIT: Adopt '--strict-image-heap' to prepare for the next GraalVM release.
HEAP: Set max heap for improved and more predictable memory usage.
CPU: Enable more CPU features with '-march=native' for improved performance.
QBM: Use the quick build mode ('-Ob') to speed up builds during development.
--------------------------------------------------------------------------------
2.3s (4.1% of total time) in 260 GCs | Peak RSS: 0.77GB | CPU load: 4.46
--------------------------------------------------------------------------------
Produced artifacts:
/Users/fu7mu4/Documents/hello-graalvm/helloworld (executable)
================================================================================
Finished generating 'helloworld' in 55.2s.
標準出力の Security report
のセクションには4つのコンポーネントが含まれているとの説明がありました。
埋めこんだSBOM情報の取り出し
埋めこんだSBOM情報は native-image-inspect
コマンドを使ってバイナリファイルから取り出します。SBOMのCychronDX形式はJSON
形式をベースにしているので、jq
で整形します。
native-image-inspect helloworld --sbom | jq .
出力結果の例です。helloworld
バイナリファイルには、4つのコンポーネント(ライブラリ)が含まれていることがわかりました。
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"group": "com.oracle.svm",
"name": "svm",
"version": "21.0.1+12",
"properties": [
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:oracle:graalvm-native-image:21.0.1+12:*:*:*:*:*:*:*"
}
]
},
{
"type": "library",
"group": "org.graalvm",
"name": "collections",
"version": "21.0.1+12",
"properties": [
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:collections:collections:21.0.1+12:*:*:*:*:*:*:*"
},
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:graalvm:collections:21.0.1+12:*:*:*:*:*:*:*"
}
]
},
{
"type": "library",
"group": "org.graalvm",
"name": "nativeimage",
"version": "21.0.1+12",
"properties": [
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:graalvm:nativeimage:21.0.1+12:*:*:*:*:*:*:*"
},
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:nativeimage:nativeimage:21.0.1+12:*:*:*:*:*:*:*"
}
]
},
{
"type": "library",
"group": "org.graalvm",
"name": "word",
"version": "21.0.1+12",
"properties": [
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:graalvm:word:21.0.1+12:*:*:*:*:*:*:*"
},
{
"name": "syft:cpe23",
"value": "cpe:2.3:a:word:word:21.0.1+12:*:*:*:*:*:*:*"
}
]
}
],
"serialNumber": "urn:uuid:74294682-8186-3741-baa7-1034007108a4"
}
確かに、名前がsvm
, collections
, nativeimage
, word
の4つのコンポーネントが含まれているようです。これらのグループを見ると、graalvm などの由来のようです。
まとめ
GraalVMを使って、Javaファイルから実行ファイルを作成する方法を確認しました。また、実行ファイルに SBOMを埋めこみ、実行ファイルからSBOMを抽出する方法を確認しました。