为什么 JDK 的镜像比 JRE 镜像小?

| docker aiauthor

TLDR:虽然 JDK 镜像内容比 JRE 镜像多,但 JDK 镜像的压缩比更高,导致最后镜像大小反而 JDK 比 JRE 小;原因是 JDK 镜像主要用于 CI/CD 流水线,对性能和耗时要求不太高,所以可以用更高的压缩比;但 JRE 镜像主要用于线上服务,对性能要求极高,因此基本没有压缩。

AI 贡献提示:技术探索过程主要由 Claude Code + Kimi K2 完成;文字部分主要为 Gemini Pro 2.5 编写;我仅提供探索方向指导和简单内容编辑。我已在能力范围内确认下述内容的准确性。如发现问题,欢迎留言反馈。

感谢 @ziqin 提出了本文的探索问题。


缘起:一个反常的发现

一位群友在运行 docker image 时发现一个反常的现象:JDK镜像(111MB)竟然比JRE镜像(139MB)小了整整28MB!

REPOSITORY                            TAG           IMAGE ID       CREATED      SIZE
bellsoft/liberica-runtime-container   jdk-21-musl   1c9d58aebbdc   2 days ago   111MB
bellsoft/liberica-runtime-container   jre-21-musl   c59d660b1633   2 days ago   139MB

但这感觉上不太合理,因为 JDK 是 JRE 的超集,不仅包含了 JRE 的全部功能,还包含了额外的开发工具(如 javac, jdb 等),为什么其镜像反倒还更小呢?这个反直觉的观察结果立刻激起了我们的好奇心。问题提出之时是个工作日的晚上,我并没有太多精力仔细思考,于是我让 Claude Code 来探索这个问题。

探案之旅:层层深入,拨开迷雾

我们的调查遵循着从宏观到微观的路径,一步步逼近问题的核心。

第一站:文件系统对比

我们首先通过 docker exec 进入两个正在运行的容器,对比其内部文件系统的差异。

初步结论:差异的根源在于Java安装目录本身。JDK镜像使用的是一个名为 liberica21-lite 的发行版,而JRE镜像使用的是 liberica21-container-jre

第二站:核心文件对比

当我们继续深入,对比两者 lib 目录下的核心文件 modules 时,差异变得更加惊人:

modules 文件是Java模块化系统的核心,存储了所有的运行时模块。JRE的modules文件竟然比JDK的大了37.8MB! 这几乎完全解释了镜像大小的差异。

但新的问题随之而来:JDK明明包含了更多的模块(如编译器 jdk.compiler、文档工具 jdk.javadoc 等),为什么它的 modules 文件反而更小?

对比项 JDK (liberica21-lite) JRE (liberica21-container-jre) 差异
模块数量 69 个 49 个 +20 个
modules 文件大小 59.3 MB 97.1 MB -37.8 MB

更多的内容,却占用了更少的空间。这背后一定有更深层次的原因。

第三站:压缩策略对比

为了彻底搞清楚 modules 文件内部的秘密,我们使用 jimage 工具将其解压,并分析了其中包含的每一个资源。真相终于水落石出。

这并非内容差异,而是压缩策略的根本不同!

观察以下对比数据:

指标 JDK (liberica21-lite) JRE (liberica21-container-jre) 证据
压缩算法 DEFLATE (zlib) DEFLATE (zlib) jimage工具确认
压缩级别 Level 9 (最大) Level 0 (无) 资源分析确认
总资源数 28,427 22,133 jimage list --verbose
压缩资源数 28,044 (98.7%) 0 (0.0%)
未压缩资源数 383 (1.3%) 22,133 (100.0%) JRE裸奔存储
压缩后总大小 60.4 MB 100.5 MB JRE因元数据开销反而变大
压缩比 2.31 : 1 0.99 : 1 JRE压缩比小于1

真正的技术根因:

  1. JDK (liberica21-lite): 使用了激进的DEFLATE压缩(相当于 jlink --compress=2,级别9)。它将所有工具和资源(包括开发工具、调试信息、所有区域设置)都包含进来,然后用最高效的算法进行压缩,以实现最小的磁盘占用。
  2. JRE (liberica21-container-jre): 完全没有使用压缩(相当于 jlink --compress=0,级别0/STORE模式)。它精心挑选了生产环境必需的运行时子集,但为了追求最快的启动速度和运行时性能,放弃了压缩。(甚至因为压缩元数据,反而引入了额外的空间开销,导致压缩比小于 1。)

设计哲学:为何如此选择?

这个看似矛盾的设计,实际上是BellSoft针对不同应用场景的深思熟虑的工程决策。

JDK “lite” 的设计目标 (--compress=2)

JRE “container-jre” 的设计目标 (--compress=0)

最终结论:一个反直觉的真理

我们最初的谜题现在有了清晰的答案:

JDK镜像小,是因为它用极致的压缩,换取了分发和存储的便利。 JRE镜像大,是因为它用空间,换取了生产环境的极致性能。

换句话说:

结语

这次从一个简单的 docker images 命令开始的探案之旅,最终带领我们深入理解了现代 Java 发行版在容器化时代的精妙设计。BellSoft Liberica 的这种差异化策略,并非一个错误,而是一个深刻理解开发者和运维者在不同阶段核心痛点的高级功能

它告诉我们,在技术的选择上,没有绝对的“好”与“坏”,只有是否“适合”。理解了这些选择背后的逻辑,我们才能在自己的工作中,做出更明智、更高效的决策。