Java jlink命令:裁剪 JDK,生成定制精简 JRE(裁剪 JVM 镜像) 作者:马育民 • 2026-10-12 09:42 • 阅读:10000 # 介绍 **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`做**链接阶段**(介于编译期、运行期之间),自动分析依赖,只包含用到的模块。 # 能力 1. **自动依赖分析**:给定模块,自动递归引入它依赖的所有模块 2. **裁剪JRE**:剔除未使用模块,产出最小运行环境 3. **内置优化插件**:压缩、删除调试信息、strip符号等进一步瘦身 4. **输出独立镜像目录**:镜像自带`bin/java`,目标机器**不需要预装JDK/JRE**,直接运行 5. 插件机制,支持扩展自定义处理逻辑 > ⚠️ 限制:**原生只支持模块化应用(带module-info.java)**;普通classpath非模块化项目,不能直接jlink,需要借助第三方工具(如jpackage、spring-boot-jlink、moditect)。 # 使用场景 1. 桌面客户端、嵌入式设备、容器镜像:打包独立Java程序,交付包体积显著降低 2. 云原生轻量镜像:构建最小Java基础镜像,减少镜像大小、攻击面 3. 分发工具类小程序:客户不需要安装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读取类。 ### 关键细节 1. jlink 的 `modules` 文件是**只读的运行时映像**,不是普通压缩包,不能直接解压。 2. 好处: - 减少文件数量; - JVM启动加载更快; - 理论上可以做死代码消除(有限); 3. 代价: - 所有模块必须是显式模块(带 `module-info.class`); - 一旦打包,想替换其中一个jar,必须**重新完整跑一遍jlink**;不能单独替换某个jar。 # 基础语法 ```bash 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手册 # 例子:精简运行环境 ```bash 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,所以要启动程序测试,防止一些需要的模块被裁剪了: ```bash runtime/bin/java -jar app.jar ``` # jlink vs jpackage - **jlink**:只生成**自定义运行时目录**,不含打包成exe/msi/dmg安装包 - **jpackage**(JDK14+):底层调用jlink,**在精简runtime基础上,打包成平台原生安装程序**(Windows exe、mac dmg、Linux deb/rpm) # 缺点与注意事项 1. 生成的runtime**平台绑定**:Windows下jlink只能产出Windows镜像,不能跨平台 2. 自定义运行时**没有自动更新机制**,版本漏洞需要重新打包分发 3. 非模块化项目不能直接jlink;SpringBoot这类传统工程需要额外工具转成模块 4. 依赖native库时需要手动处理,jlink不会自动识别第三方本地库 原文出处:/show_1GW4DSHCVvfg.html