Companion code for "Dockerizing Spring Boot 4: Layered Jars, Buildpacks, Distroless and Image Size Benchmarks". Fat jar on JDK and JRE, layered jar on Debian, Alpine and distroless, jlink, the JDK 25 AOT cache, Paketo buildpacks and Jib, each measured for size on disk and pushed, rebuild delta, startup, user and shell. Also PID 1 and signal handling, the jdeps module gap, AOT cache mismatches and buildpacks memory calculation. Transcripts in docs/output/, regenerated by scripts/run-all.sh. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
46 lines
2.3 KiB
Markdown
46 lines
2.3 KiB
Markdown
# 5. jlink: 133 MB smaller, 15 metrics quieter
|
|
|
|
[← 4. Buildpacks](04-buildpacks.md) · [Index](../README.md) · Next: [6. AOT cache →](06-aot-cache.md)
|
|
|
|
[`Dockerfile.jlink-distroless`](../docker/Dockerfile.jlink-distroless) asks `jdeps` which JDK modules
|
|
the application needs and builds a runtime with only those:
|
|
|
|
```bash
|
|
jdeps --ignore-missing-deps -q --recursive --multi-release 25 --print-module-deps \
|
|
--class-path 'extracted/dependencies/lib/*' extracted/application/application.jar
|
|
```
|
|
|
|
It chose 18 modules ([`jlink-metrics.txt`](output/jlink-metrics.txt)) - including `java.desktop`,
|
|
because Spring uses `java.beans`. On `distroless/java-base` the image is **119 MB** on disk against
|
|
252 MB for distroless with the full JRE.
|
|
|
|
## It runs. It is also missing something.
|
|
|
|
The jdeps-only image starts, serves requests and passes its health check. Its startup log has two
|
|
warnings:
|
|
|
|
```
|
|
i.m.c.i.binder.jvm.JvmGcMetrics : GC notifications will not be available because com.sun.management.GarbageCollectionNotificationInfo is not present
|
|
i.m.c.i.binder.jvm.JvmGcMetrics : GC notifications will not be available because no GarbageCollectorMXBean of the JVM provides any. GCs=[G1 Young Generation, G1 Concurrent GC, G1 Old Generation]
|
|
```
|
|
|
|
and `/actuator/prometheus` exports 46 metric names instead of 61. Gone: `jvm_gc_pause_seconds_*`,
|
|
`jvm_gc_memory_allocated_bytes_total`, `jvm_gc_live_data_size_bytes`, `process_cpu_usage`,
|
|
`system_cpu_usage`, `process_files_open_files` and more - 15 in total.
|
|
|
|
`com.sun.management.*` lives in the `jdk.management` module, and Micrometer touches it
|
|
reflectively, which `jdeps` cannot see. Nothing fails; the GC and CPU panels of your dashboard just
|
|
go flat after the image switch. The Dockerfile now adds it by default:
|
|
|
|
```dockerfile
|
|
ARG EXTRA_MODULES="jdk.management"
|
|
```
|
|
|
|
With it, the only difference left is `jvm_gc_concurrent_phase_time_*`, which Micrometer registers
|
|
lazily inside its GC notification listener - it appears after the first G1 concurrent cycle, which
|
|
may or may not have happened a few seconds after startup, in either image.
|
|
|
|
The general rule: diff the metric names, not just the health check, before shipping a jlink image.
|
|
Other modules commonly needed only reflectively are `jdk.crypto.cryptoki` (PKCS#11),
|
|
`jdk.localedata` (non-English locale data) and `jdk.naming.dns` (DNS lookups through JNDI).
|