登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java jlink 运行时镜像为什么不包含完整JDK

来源:17golang原创

时间:2026-10-31 02:12:54 500浏览 收藏

用 jlink 生成的运行时镜像没有完整 JDK,是因为它的目标从来不是复制 JDK,而是根据指定的根模块和传递依赖组装一个“够应用运行”的自定义运行时。javac、javadoc、头文件和部分调试资源属于开发或诊断工具,不会因为它们存在于原始 JDK 中就自动进入镜像。

官方地址:https://docs.oracle.com/en/java/javase/25/docs/specs/man/jlink.html

判断 jlink 结果时,先看应用的模块依赖闭包,再看是否显式添加了额外模块;不要用“镜像里没有 javac”反推 jlink 生成失败。

先区分 JDK、运行时镜像和应用模块

完整 JDK 是开发工具集合,除了运行 Java 程序需要的模块,还包含编译、打包、诊断和文档工具。jlink 面向的是部署阶段:它从模块路径中找到应用模块,把根模块以及依赖图中可达的模块放入输出目录。

因此,镜像里常见的是 java 启动器和应用需要的标准模块,例如 java.base、java.logging;而 javac、javadoc、jar 等开发命令并不是普通应用运行的必需依赖。缺少它们,通常只是说明镜像边界生效了。

应用模块、根模块、传递依赖与开发工具之间的静态边界关系说明图
图1:应用模块通过根模块和传递依赖形成运行时闭包,开发工具位于默认输出边界之外;这是静态结构说明图,不是运行截图。

jlink 为什么不会自动复制完整 JDK

--add-modules 指定的是根模块,jlink 再根据模块声明解析传递依赖。它不是“从 JDK 目录里挑文件”,也不是“把所有可观察模块都打包”。官方命令说明明确指出,输入模块可以来自模块化 JAR、JMOD 或 exploded module,输出内容由指定模块及其传递依赖构成。

例如,应用模块声明只依赖日志能力时,可以这样构建一个运行时镜像:

# 只把应用模块和它声明的运行时依赖放进镜像
jlink \
  --module-path mods:$JAVA_HOME/jmods \
  --add-modules com.example.app \
  --strip-debug \
  --no-man-pages \
  --no-header-files \
  --output runtime

# 运行时镜像的启动器位于输出目录的 bin 下
./runtime/bin/java --module com.example.app/com.example.app.Main

这里的关键不是参数数量,而是根模块 com.example.app。如果它的 module-info.java 只写了 requires java.logging,那么 java.logging 会被解析;没有被依赖图触及的开发模块则不会因为“同属一个 JDK”而加入。

先用两个命令判断到底缺了什么

遇到镜像启动失败时,我会先分别确认镜像里有什么,以及应用声明了什么。第一个命令观察输出镜像的模块列表,第二个命令从应用或依赖 JAR 推导模块依赖:

# 查看镜像实际包含的模块,避免凭目录名称猜测
./runtime/bin/java --list-modules

# 从模块化 JAR 推导需要的 JDK 模块;路径替换成自己的应用文件
jdeps --print-module-deps --ignore-missing-deps app.jar

如果 jdeps 给出的依赖包含 java.desktop,但构建命令的根模块没有把它带入依赖闭包,就应该显式加入或修正模块声明。如果问题是找不到 javac,则要先确认部署包是否真的需要编译;一个只负责运行已编译程序的镜像不应把编译器当作默认依赖。

需要额外能力时,显式添加模块

“不包含完整 JDK”不等于“不能使用额外能力”。图形界面、区域设置、命名服务等能力都可以按应用场景加入。做法是把真正需要的模块写在 --add-modules 中,或让应用模块的 requires 关系表达它们。

# GUI 应用显式加入桌面模块,并保留中文与英文等区域设置
jlink \
  --module-path mods:$JAVA_HOME/jmods \
  --add-modules com.example.desktop,jdk.localedata \
  --include-locales=zh-CN,en \
  --strip-debug \
  --output desktop-runtime

# 只保留需要的压缩级别;2 表示 ZIP 资源压缩
jlink \
  --module-path mods:$JAVA_HOME/jmods \
  --add-modules com.example.app,java.logging \
  --compress=2 \
  --output compact-runtime

--include-locales 使用时要同时加入 jdk.localedata。这类参数体现的是“按需加能力”,不是恢复完整 JDK;每加入一个模块,都应能在应用功能或服务发现需求中找到理由。

必需依赖、按需加入模块、服务提供者与运行时镜像之间的静态关系说明图
图2:按需模块和服务绑定都需要显式配置,最终仍然汇入面向部署的运行时镜像;这是结构示意图,不是软件截图。

服务发现为什么还可能少模块

如果应用通过服务加载机制使用实现,而不是直接在模块声明中引用实现模块,仅列出应用模块有时还不够。jlink 提供 --bind-services,用于把服务提供者模块及其依赖纳入链接范围。

# 应用使用 ServiceLoader 时,按需绑定可观察的服务提供者
jlink \
  --module-path mods:$JAVA_HOME/jmods \
  --add-modules com.example.app \
  --bind-services \
  --output service-runtime

这仍然不是“把所有 JDK 服务都塞进来”。它只改变服务绑定这一类解析行为,镜像依旧围绕应用模块、可观察模块和依赖闭包生成。JDK 25 的 jpackage 默认也不再自动包含服务绑定;如果包装应用确实需要它,应通过 jlink 选项明确传入。

把镜像构建当作可升级的交付物

自定义运行时不是一次生成、永久不变的压缩包。官方文档把更新责任交给开发者,所以构建脚本应固定 JDK 工具链、记录根模块,并在依赖变化后重新生成镜像。

现象优先判断处理方式
镜像没有 javac是否只是部署运行通常保留最小运行时,不补完整 JDK
启动时报缺少标准模块模块依赖闭包是否遗漏用 jdeps 和 --list-modules 对照后显式加入
ServiceLoader 找不到实现是否需要服务绑定评估 --bind-services 或明确加入提供者模块
Locale 结果不完整是否缺少 jdk.localedata加入模块并设置 --include-locales

我的经验是把“缺少完整 JDK”拆成两个问题:应用运行是否缺少模块,以及团队是否误把开发工具当成运行时依赖。前者通过模块依赖和命令检查解决,后者通过重新确认交付边界解决。

相关问题

jlink 生成的镜像能不能继续编译 Java 代码?

默认不能把它当作完整开发环境。镜像的目标是运行应用;需要编译时应使用单独的 JDK,或明确评估是否真的要把开发工具放入交付环境。

为什么加了 java.desktop 仍然启动失败?

还要检查应用自己的模块是否在模块路径中、依赖是否可解析,以及是否存在其他缺少的模块。不要只根据一个报错模块停止判断。

是否应该直接使用完整 JDK 避免排查?

开发机和排障环境可以使用完整 JDK,但生产分发通常更适合按应用依赖生成镜像;这样边界清楚,升级也可以在构建流程中重复完成。

参考文档:https://docs.oracle.com/en/java/javase/25/docs/specs/man/jlink.html

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>