介绍
jlink(Java Linker),JDK9 引入(JEP 282),属于Java模块化系统(Project Jigsaw)配套工具,用来把一组模块及其传递依赖打包、优化,生成自定义精简运行时镜像(custom runtime image)。
一句话解释
不再分发完整JRE/JDK,只打包你的程序真正需要的Java模块,大幅缩小体积。
背景
- JDK8及之前:发布Java程序,要么要求客户本机装JRE,要么打包完整JRE(体积很大,几百MB)。
- JDK9+模块化:Java本身拆成大量独立模块(
java.base、java.net等),jlink做链接阶段(介于编译期、运行期之间),自动分析依赖,只包含用到的模块。
能力
- 自动依赖分析:给定模块,自动递归引入它依赖的所有模块
- 裁剪JRE:剔除未使用模块,产出最小运行环境
- 内置优化插件:压缩、删除调试信息、strip符号等进一步瘦身
- 输出独立镜像目录:镜像自带
bin/java,目标机器不需要预装JDK/JRE,直接运行 - 插件机制,支持扩展自定义处理逻辑
⚠️ 限制:原生只支持模块化应用(带module-info.java);普通classpath非模块化项目,不能直接jlink,需要借助第三方工具(如jpackage、spring-boot-jlink、moditect)。
使用场景
- 桌面客户端、嵌入式设备、容器镜像:打包独立Java程序,交付包体积显著降低
- 云原生轻量镜像:构建最小Java基础镜像,减少镜像大小、攻击面
- 分发工具类小程序:客户不需要安装Java环境,解压即用
jlink 打包两种模式
当 jar 参与 jlink 链接(放到 --module-path):
jlink 会把这个模块里面所有的 .class、资源文件,抽取出来,打包进 runtime/lib/modules 这个单一的二进制映像文件里面。
打包完成之后,你在输出目录再也看不到原来的 xxx.jar 文件了。类文件被合并、压缩到 JVM 的内部模块镜像里。
modules不是 zip/jar,是 JLink 专用的二进制容器,是JVM启动时直接映射读取的。
模式1:第三方jar【参与jlink链接】
执行完 jlink 输出 runtime:
runtime/
├─ bin/
│ └─ java.exe
└─ lib/
├─ modules ← 【所有JDK模块 + 你的第三方模块的类全部塞在这里】
└─ ...其他配置
✅ 原来的 ofdrw-converter.jar、flatlaf.jar 消失了。
所有class都被打进 modules。启动方式:java -m 模块名/主类。
JVM 不再去读独立jar,直接从 modules 里面加载类。
模式2:第三方jar【不参与jlink链接】
jlink只裁剪JDK,之后手动复制jar进去:
runtime/
├─ bin/
│ └─ java.exe
├─ lib/
│ └─ modules ← 这里**只有裁剪后的JDK内置模块**
└─ app/
├─ ofdrw-converter.jar
├─ aspose-pdf.jar
├─ flatlaf.jar
└─ your-app.jar
lib/modules 里面只有JDK的类。
你的所有第三方jar还在 app/ 目录,是独立文件。启动用 java -cp app/* 主类,JVM 从这些独立jar读取类。
关键细节
- jlink 的
modules文件是只读的运行时映像,不是普通压缩包,不能直接解压。 - 好处:
- 减少文件数量;
- JVM启动加载更快;
- 理论上可以做死代码消除(有限);
- 代价:
- 所有模块必须是显式模块(带
module-info.class); - 一旦打包,想替换其中一个jar,必须重新完整跑一遍jlink;不能单独替换某个jar。
- 所有模块必须是显式模块(带
基础语法
jlink [选项] --module-path <模块路径> --add-modules <模块名> --output <输出目录>
--module-path:模块查找路径;JDK内置模块在$JAVA_HOME/jmods;自己编译的模块放在mlib--add-modules:入口模块,会自动带上全部传递依赖--output:生成自定义运行时的文件夹- 常用可选参数
--compress=2:压缩级别(0无压缩,1常量字符串,2全部)--strip-debug:移除调试信息,减小体积--no-header-files:不包含头文件--no-man-pages:不包含man手册
例子:精简运行环境
jlink --module-path "%JAVA_HOME%\jmods" ^
--add-modules java.base,java.desktop,java.xml,java.logging,jdk.crypto.ec,jdk.unsupported ^
--output runtime ^
--strip-debug ^
--compress 2 ^
--no-man-pages ^
--no-header-files
解释
1. --module-path "%JAVA_HOME%\jmods"
模块搜索路径。%JAVA_HOME%\jmods 目录存放 JDK 内置所有模块(.jmod 文件),告诉 jlink 去哪里读取 JDK 模块。
2. --add-modules 模块列表
指定要打包进精简 JRE 的模块,会连带打包它们的传递依赖
java.base:所有 Java 程序的基础核心模块,必选。所有类、基础 IO、基础集合都在这里。java.desktop:聚合模块,一次性带入 AWT / Swing / ImageIO / 字体 / 打印,你的 GUI 窗口、图片渲染依赖它。java.xml:XML 解析,OFD 文件本质是 XML,解析 OFD 需要。java.logging:Java 内置日志模块。jdk.crypto.ec:椭圆曲线加密。OFD/PDF 的签名、证书校验会用到。jdk.unsupported:保留sun.misc.Unsafe等 JDK8 遗留内部 API。Aspose、OFDRW 这类文档库强依赖这个模块,缺少会运行时报错。
注意:
--add-modules会自动递归引入这些模块依赖的其他模块。
3. --output runtime
输出目录,精简后的 JRE 会全部生成到 runtime 文件夹。
⚠️ 重要:执行这条命令前,必须手动删除旧的 runtime 文件夹,否则旧文件残留,结果异常。
4. --strip-debug
剥离调试信息(调试符号、行号、局部变量信息)。
- 减小
lib/modules体积 - 不影响正常业务运行;出错时异常堆栈还能看到类名方法名,只是丢失源码行号。
5. --compress 2
对lib/modules开启最高级 ZIP 压缩。
0:不压缩;1:字符串共享;2:Deflate 压缩,文件最小- 仅压缩
lib/modules(存放 Java class);bin 目录的 dll、exe 完全不压缩 - 代价:类首次加载需要微小解压开销,GUI 程序几乎感知不到。
6. --no-man-pages
不复制 man 手册(Windows 没用,直接跳过,减少体积)。
7. --no-header-files
不复制 C 语言头文件(开发 JNI 才需要,普通 Java 运行环境不需要,删掉减少体积)。
删除无用的可执行文件
只保留java、javac两个可执行文件,其余全删除:
echo 删除不需要的工具exe
del /Q runtime\bin\jpackage.exe
del /Q runtime\bin\jlink.exe
del /Q runtime\bin\jmod.exe
del /Q runtime\bin\jdeps.exe
del /Q runtime\bin\javap.exe
del /Q runtime\bin\javadoc.exe
del /Q runtime\bin\jar.exe
del /Q runtime\bin\keytool.exe
del /Q runtime\bin\rmiregistry.exe
del /Q runtime\bin\image.exe
del /Q runtime\bin\jfr.exe
del /Q runtime\bin\jdeprscan.exe
del /Q runtime\bin\jarsigner.exe
del /Q runtime\bin\javac.exe
del /Q runtime\bin\serialver.exe
del /Q runtime\bin\jaccesswalker.exe
del /Q runtime\bin\jaccessinspector.exe
del /Q runtime\bin\jabswitch.exe
del /Q runtime\bin\ktab.exe
del /Q runtime\bin\klist.exe
del /Q runtime\bin\kinit.exe
运行程序
裁剪 JDK,生成定制精简 JRE,所以要启动程序测试,防止一些需要的模块被裁剪了:
runtime/bin/java -jar app.jar
jlink vs jpackage
- jlink:只生成自定义运行时目录,不含打包成exe/msi/dmg安装包
- jpackage(JDK14+):底层调用jlink,在精简runtime基础上,打包成平台原生安装程序(Windows exe、mac dmg、Linux deb/rpm)
缺点与注意事项
- 生成的runtime平台绑定:Windows下jlink只能产出Windows镜像,不能跨平台
- 自定义运行时没有自动更新机制,版本漏洞需要重新打包分发
- 非模块化项目不能直接jlink;SpringBoot这类传统工程需要额外工具转成模块
- 依赖native库时需要手动处理,jlink不会自动识别第三方本地库