Java jlink 运行时镜像为什么不包含完整JDK
用 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 等开发命令并不是普通应用运行的必需依赖。缺少它们,通常只是说明镜像边界生效了。

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;每加入一个模块,都应能在应用功能或服务发现需求中找到理由。

服务发现为什么还可能少模块
如果应用通过服务加载机制使用实现,而不是直接在模块声明中引用实现模块,仅列出应用模块有时还不够。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
PHP grapheme_strpos 的偏移量为什么不按字节计算
- 上一篇
- PHP grapheme_strpos 的偏移量为什么不按字节计算
- 下一篇
- Go go-shp 怎样遍历Shapefile中的几何记录
-
- 文章 · java教程 | 1小时前 | Java ·
- Java jlink 怎样只打包应用需要的模块
- 353浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java PropertyChangeSupport 新旧值相等时为什么可能不触发通知
- 408浏览 收藏
-
- 文章 · java教程 | 7小时前 |
- Java PropertyChangeSupport 怎样只监听指定属性
- 420浏览 收藏
-
- 文章 · java教程 | 7小时前 | java Introspector stopClass JavaBeans
- Java Introspector 的 stopClass 怎样排除继承属性
- 145浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java Introspector 怎样取得 JavaBean 的属性描述
- 447浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java ImageReader 为什么需要同时处理输入流关闭和 dispose
- 176浏览 收藏
-
- 文章 · java教程 | 9小时前 |
- Java ImageReader 怎样只读取图片尺寸而不解码整图
- 273浏览 收藏
-
- 文章 · java教程 | 9小时前 |
- Java PrintService 怎样查询打印属性的支持范围
- 340浏览 收藏
-
- 文章 · java教程 | 10小时前 |
- Java PrintService 怎样确认打印机接受的文档类型
- 114浏览 收藏
-
- 文章 · java教程 | 10小时前 | Java教程 · java MIDI设备 MidiSystem MidiDevice.Info javax.sound.midi
- Java MidiSystem 怎样枚举可用的 MIDI 设备
- 172浏览 收藏
-
- 文章 · java教程 | 10小时前 |
- Java MidiSystem 怎样读取 MIDI 序列中的轨道
- 489浏览 收藏
-
- 文章 · java教程 | 11小时前 | Java · AudioInputStream Java AudioSystem AudioFileFormat getFrameLength
- Java AudioSystem 怎样读取音频文件的帧长度
- 302浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 801次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 792次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 879次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 849次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 680次使用
-
- Java 时间格式兼容多个输入格式的方法
- 2026-10-18 501浏览
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览

