# 为什么 JDK 的镜像比 JRE 镜像小？

LLMS 索引： [llms.txt](/llms.txt)

---

TLDR：虽然 JDK 镜像内容比 JRE 镜像多，但 JDK 镜像的压缩比更高，导致最后镜像大小反而 JDK 比 JRE 小；原因是 JDK 镜像主要用于 CI/CD 流水线，对性能和耗时要求不太高，所以可以用更高的压缩比；但 JRE 镜像主要用于线上服务，对性能要求极高，因此基本没有压缩。

AI 贡献提示：技术探索过程主要由 Claude Code + Kimi K2 完成；文字部分主要为 Gemini Pro 2.5 编写；我仅提供探索方向指导和简单内容编辑。我已在能力范围内确认下述内容的准确性。如发现问题，欢迎留言反馈。

感谢 [@ziqin](https://github.com/ziqin/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` 进入两个正在运行的容器，对比其内部文件系统的差异。

- **JDK 镜像**: `docker exec jdk-analysis du -sh /usr/lib/jvm/liberica21-lite` -> **98.5M**
- **JRE 镜像**: `docker exec jre-analysis du -sh /usr/lib/jvm/liberica21-container-jre` -> **125.2M**

**初步结论**：差异的根源在于Java安装目录本身。JDK镜像使用的是一个名为 `liberica21-lite` 的发行版，而JRE镜像使用的是 `liberica21-container-jre`。

### 第二站：核心文件对比

当我们继续深入，对比两者 `lib` 目录下的核心文件 `modules` 时，差异变得更加惊人：

- **JDK `modules` 文件**: 59.3MB
- **JRE `modules` 文件**: 97.1MB

`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`)
-   **目标场景**: CI/CD流水线、开发环境、容器构建阶段。
-   **优化核心**: **存储和网络效率**。在这些场景下，镜像的下载速度和存储成本是首要考虑因素。构建时的一次性压缩CPU开销，可以换来后续无数次快速的分发和部署。
-   **策略**: **空间换时间（构建时）**。牺牲构建时的CPU时间，换取最小的存储空间。
### JRE "container-jre" 的设计目标 (`--compress=0`)
-   **目标场景**: 生产环境运行时。
-   **优化核心**: **运行时性能**。在生产环境中，应用的启动速度、内存占用和CPU效率至关重要。免去解压步骤，意味着更快的类加载、更低的CPU消耗和更少的内存抖动。
-   **策略**: **时间换空间（运行时）**。牺牲磁盘空间，换取运行时的高性能和稳定性。

## 最终结论：一个反直觉的真理

我们最初的谜题现在有了清晰的答案：
> **JDK镜像小，是因为它用极致的压缩，换取了分发和存储的便利。**
> **JRE镜像大，是因为它用空间，换取了生产环境的极致性能。**

换句话说：
-   **更多内容 + 更强压缩 = 更小的分发体积 (JDK)**
-   **更少内容 + 无压缩 = 更大的分发体积 (JRE)**

## 结语

这次从一个简单的 `docker images` 命令开始的探案之旅，最终带领我们深入理解了现代 Java 发行版在容器化时代的精妙设计。BellSoft Liberica 的这种差异化策略，并非一个错误，而是一个深刻理解开发者和运维者在不同阶段核心痛点的**高级功能**。

它告诉我们，在技术的选择上，没有绝对的“好”与“坏”，只有是否“适合”。理解了这些选择背后的逻辑，我们才能在自己的工作中，做出更明智、更高效的决策。
