☰
OpenSDK 21 解压、JAVA_HOME 与构建工具版本锁定
2026/10/23 5:57:49 网站建设 项目流程

简介:OpenJDK 21 完整压缩包面向需要搭建 Java 开发环境、体验新版语言与 API 的开发者,也适合教学演示与项目迁移前的兼容性验证。压缩包共 484 个文件,整体约 188.28MB,包含 exe 可执行工具、dll 动态库、jmod 模块文件,以及许可证、附加许可说明和 Markdown 文档等,覆盖编译器、运行时与标准库模块所需组件。资源已有 207 人学习下载,可作为获取 OpenJDK 21 的现成归档。其价值在于免去逐项搜集零散文件,解压后即可获得较完整的 JDK 目录结构和工具链,便于对照新版 Java 特性、检查模块组成、开展课程实验或本地开发环境配置;随包许可与说明文档也有助于了解分发和合规边界。适合具备一定 Java 基础、关注 OpenJDK 21 实践与版本兼容性的读者留存备用。

1. 先把 OpenSDK 21 下载文件解压再谈安装

很多人拿到一个 OpenSDK 21 下载文件,第一反应是双击、一路下一步。真拆过包就知道,这类 JDK 发行版九成以上就是一个压缩包,解压即用,不写系统目录、不注册服务。装与不装的差别,只是你把它解压到哪个路径。真正让人翻车的从来不是“怎么装”,而是装完之后的三个问题:JAVA_HOME 指向了谁、PATH 里谁排在前面、构建工具到底拿哪个 JDK 去编译。这三个问题通常不会当场报错,而是等到上线前十分钟,以一个 UnsupportedClassVersionError 的形式集中爆发。

这份 OpenSDK 21 下载文件对应的场景很明确:要在本地开发机或构建机上跑 JDK 21,又不想被安装程序绑死的人。用它你能拿到一个干净的 LTS 运行时,随时切换版本,也方便塞进构建镜像里。下面按“拆包 → 配环境 → 锁构建 → 排错 → 裁剪验证”的顺序走一遍。

2. 拆开这个 OpenSDK 21 下载文件:目录结构、校验与第一轮验证

拿到压缩包之后先别急着解压到/usr/local,先花两分钟看看里面到底有什么。这一步的价值在于:你后面所有的排错,都是围绕这些目录展开的。比如bin里的 java 和jmods里的模块文件是不是同一个版本、lib下的tools.jar在 21 里早就没了、证书库cacerts到底在哪个路径,这些细节决定了你遇到问题时是五分钟解决还是查一下午。

2.1 先认路:bin、lib、jmods、conf 各管什么

把压缩包解开,典型结构是这几块:

目录作用排错时的用法
bin/java、javac、jar、jlink、jpackage、keytool 等可执行文件确认java -version走的是哪一个
lib/运行时类库、modules镜像、cacerts证书库HTTPS 握手失败先看lib/security/cacerts
jmods/模块化的.jmod文件,jlink 裁剪时的原料jlink 报找不到模块,八成是这里缺文件
conf/security、net.properties等配置调代理、调 TLS 版本改这里,不动 jar
legal/各模块的授权声明合规审查时看,不影响运行
release纯文本的版本元信息用脚本读取版本号,比解析java -version稳

jmods是最容易被忽略的一块。有人为了省空间只留bin和lib,结果 jlink 直接罢工——因为裁剪运行时的输入就是jmods里的模块。你要只是跑现成应用,删了也没事;一旦要做最小运行时打包,它就是命根子。

# 不解压先看结构,确认包没下错、也没被塞乱七八糟的东西 tar -tzf opensdk-21_linux-x64.tar.gz | head -n 30 # 只看顶层目录,快速判断布局 tar -tzf opensdk-21_linux-x64.tar.gz | awk -F/ 'NF<=2' | sort -u

第一条命令输出前 30 行,主要看顶层目录名是不是预期的解压目录;第二条用 awk 过滤层级,能一眼看出包里有没有多套 JDK 混在一起。有些构建产物会把jdk-21目录再套一层jdk-21.0.x,这个嵌套差异会直接导致你的软链接指错位置,后面 JAVA_HOME 怎么配都不生效。

2.2 用哈希清单和 release 文件确认包是完整的

下载文件损坏这事听起来玄学,但在大包 + 弱网络的环境里并不罕见,表现是解压到一半报unexpected EOF,或者解压完了java -version直接段错误。稳妥做法是拿包内附带的哈希清单核对一遍。

# 假设包的同一目录下附带了 sha256 清单文件 sha256sum -c opensdk-21_linux-x64.tar.gz.sha256 # 没有清单就自己算一份,和发布页上给的值对 sha256sum opensdk-21_linux-x64.tar.gz

-c参数是让 sha256sum 读取清单文件里记录的“哈希 + 文件名”并自动比对,输出OK才算过关。Windows 上用certutil -hashfile 文件名 SHA256达到同样效果。这一步花不了三十秒,能省掉后面“到底是环境问题还是包坏了”的反复怀疑。

解压之后紧接着读release文件,它是纯 key=value 文本,解析起来比java -version的输出稳得多:

tar -xzf opensdk-21_linux-x64.tar.gz -C /opt cd /opt/jdk-21 # release 里有版本号、构建号、OS 架构,脚本里读它最省事 cat release # 单独取版本号,用于写进构建元数据 grep '^JAVA_VERSION=' release | cut -d'"' -f2

JAVA_VERSION的值形如21.0.x,IMPLEMENTOR和IMPLEMENTOR_VERSION记录的是构建方信息,做多环境比对时特别有用——线上跑的和本地跑的不是同一个构建,日志里对不上号是常见事故源。

2.3 第一次跑起来:三条命令确认运行时真的是 21

解压完不要急着配环境变量,先用绝对路径跑一次。这样能排除 PATH 干扰,把“包本身能不能跑”和“环境配没配对”两件事分开。

# 1. 看版本,输出里必须出现 21 /opt/jdk-21/bin/java -version # 2. 看模块总数,完整包通常在 70 个上下,明显偏少说明包被裁过 /opt/jdk-21/bin/java --list-modules | wc -l # 3. 用 jshell 跑一段 21 才有的语法,验证不是旧版本换了皮 /opt/jdk-21/bin/jshell -q <<'EOF' record Point(int x, int y) {} Object o = new Point(1, 2); if (o instanceof Point(int x, int y)) System.out.println(x + y); /exit EOF

第一条输出的第一行形如openjdk version "21.0.x",重点看引号里的主版本号。第二条统计模块数量,java --list-modules列出的是当前运行时实际可用的模块,数量骤减往往意味着拿到的是 jlink 裁过的运行时而非完整 JDK——这种包能跑应用,但没法编译,javac 可能压根不存在。

第三条是关键的交叉验证:record 模式匹配在 21 里是正式特性,在 17 上直接编译报错。用 jshell 而不是写文件编译,是因为 jshell 启动快、不落盘,适合当“这包到底是不是 21”的快速探针。如果这段代码报语法错误,那基本可以确定包名写着 21、内容不是。

还有一点值得留意:bin目录下有没有javac。只带运行时的包(JRE 形态)在某些发行版里依然存在,如果你的用途是编译,拿到这种包会卡在第一步。用ls bin | grep -E 'javac|jlink|jpackage'扫一眼,三个都在就是完整 JDK。

3. 环境变量落地:JAVA_HOME、PATH 与多版本共存的三套配置

包能跑了,接下来才轮到环境变量。这块的坑集中在两个地方:一是改了配置文件但当前 shell 没重新加载,二是系统里已经有一份旧 JDK,PATH 顺序导致新的没生效。判断标准很简单——which java和echo $JAVA_HOME输出的路径必须指向同一个目录,不一致就是配置没落地。

3.1 Linux 与 macOS:profile.d 加软链接的组合

服务器上我一般不直接改~/.bashrc,而是往/etc/profile.d/丢一个独立脚本。原因很实际:~/.bashrc只对交互式非登录 shell 生效,而构建机上的 CI 进程往往是登录 shell 或者压根不读 bashrc,写在 profile.d 里覆盖面最广,也方便统一管理。

# /etc/profile.d/jdk21.sh export JAVA_HOME=/opt/jdk-21 export PATH="$JAVA_HOME/bin:$PATH" # 可选:给 Maven 之类依赖 JAVA_HOME 的工具兜底 export CLASSPATH=.

写完之后source /etc/profile.d/jdk21.sh或者重新登录生效。注意PATH里$JAVA_HOME/bin必须放在最前面,否则系统自带的/usr/bin/java会抢先命中。这不是理论问题,绝大多数“我明明配了 21 但 java -version 还是 17”都是这一条引起的。

macOS 上情况略有不同,系统自带一层 java 命令的包装器,/usr/bin/java只是个转发器。用/usr/libexec/java_home -V能列出系统认识的所有 JDK,再用-v 21取路径。但如果你把 OpenSDK 直接解压在随便一个目录,这个命令未必认得到,需要把解压目录软链到/Library/Java/JavaVirtualMachines/下,命名成xxx.jdk/Contents/Home这种结构。

# 让系统层面的 java 命令也能找到这份 21 sudo ln -sfn /opt/jdk-21 /Library/Java/JavaVirtualMachines/opensdk-21.jdk/Contents/Home # 验证系统视角下 21 的路径 /usr/libexec/java_home -v 21

-v后面跟主版本号,输出的是匹配到的最优路径。这个路径可以直接塞进 IDE 的 JDK 配置框,比手点目录靠谱。软链接的-sfn三个参数分别是软链、强制覆盖、把目标当目录处理,缺一个都可能出现“链接套链接”的怪状。

3.2 Windows:用户变量优先,别动系统变量

Windows 上装多份 JDK 的人最容易犯的错,是直接改“系统变量”里的 JAVA_HOME。改完发现某些终端生效、某些不生效,因为不同进程继承的环境变量快照不一样。我的习惯是改用户变量,然后用 PowerShell 显式刷新当前会话。

# 写入用户级环境变量,不需要管理员权限 [Environment]::SetEnvironmentVariable("JAVA_HOME", "D:\sdk\jdk-21", "User") # 把 bin 放到用户 PATH 的最前面,避免被旧版本抢占 $oldPath = [Environment]::GetEnvironmentVariable("Path", "User") $newPath = "D:\sdk\jdk-21\bin;" + ($oldPath -split ';' | Where-Object { $_ -notmatch 'jdk|java' }) -join ';' [Environment]::SetEnvironmentVariable("Path", $newPath, "User") # 只刷新当前窗口的会话变量,不用重开终端 $env:JAVA_HOME = "D:\sdk\jdk-21" $env:Path = "D:\sdk\jdk-21\bin;" + $env:Path

"User"这个参数是作用域,可选User、Machine、Process。选User的好处是不污染全局、不需要提权,坏处是服务类进程(以 SYSTEM 身份跑的)读不到,那种场景才需要写Machine。PATH 那段用了管道过滤,把历史残留的 jdk、java 路径先剔掉再加新的,避免 PATH 越滚越长、最后超出长度限制。

这里有个血泪经验:改完别急着开新窗口验证,先在当前窗口执行$env:JAVA_HOME看值对不对。如果你改的是注册表层面的用户变量,新开的窗口才会读到;如果改的是$env:前缀的会话变量,只有当前窗口有效。搞混这两层,就会出现“刚才还好好的,重开一个终端又变回去了”的诡异现象。

3.3 多版本共存:切换脚本比反复改系统变量靠谱

手上有 17 和 21 两份 JDK 是常态,尤其是维护老项目的同时开新项目。反复去改系统环境变量属于自找麻烦,正确姿势是保留一份“基准 JAVA_HOME”,用一个脚本切换软链接。

#!/usr/bin/env bash # /usr/local/bin/switch-jdk set -euo pipefail JDK_ROOT=/opt LINK=/opt/current-jdk case "${1:-}" in 17|21) target="$JDK_ROOT/jdk-$1" ;; *) echo "用法: switch-jdk {17|21}" exit 1 ;; esac if [ ! -x "$target/bin/java" ]; then echo "目标不存在或不可执行: $target/bin/java" >&2 exit 2 fi ln -sfn "$target" "$LINK" "$LINK/bin/java" -version

脚本的核心是ln -sfn这一步:它把/opt/current-jdk这个固定路径重新指向新的解压目录,而环境变量里只需要写死JAVA_HOME=/opt/current-jdk。切版本变成改一个软链接,不用再 source 任何配置文件,也不影响已经启动的进程。

set -euo pipefail三个开关分别对应:遇错退出、引用未定义变量报错、管道中任一环节失败即失败。这种运维小脚本加这三行,能挡掉绝大多数“脚本跑一半默默失败”的坑。-x测试的是可执行权限,比-d更严格,能提前发现解压时权限丢失的问题。

切完记得看两件事:readlink -f /opt/current-jdk确认软链指向,java -version确认生效。如果版本没变,先查hash -r清一下 shell 的命令缓存——bash 会缓存可执行文件的绝对路径,换了软链它还认老的,这个缓存机制坑过不少人。

4. 让构建工具认死 21:Maven 与 Gradle 的 toolchain 参数

环境变量只解决“命令行里敲 java 用的是哪个”,解决不了“构建工具拿哪个 JDK 编译”。这两件事是分开的:JAVA_HOME 决定 Maven 自己跑在哪个 JVM 上,而编译目标版本由插件配置决定。三者不一致,就会出现“运行时是 21,编译产物却是 17 字节码”,或者反过来“本地编译过了、CI 上跑不起来”。

4.1 Maven:release 参数和 toolchains 两条路

最省事的做法是在 pom 里锁release,而不是source和target分开写。release会让编译器同时校验 API 和字节码版本,用到了高于目标版本的 API 会直接编译失败,属于“提前报错”的好设计。

<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <release>${maven.compiler.release}</release> <compilerArgs> <arg>-Xlint:all</arg> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins> </build>

release是 JDK 9 之后引入的参数,替代了过去的source/target组合。-parameters保留方法参数名,反射和部分框架依赖它,不加会在运行时报参数名为 arg0、arg1。-Xlint:all打开全部告警,21 里对废弃 API 的提示比老版本更积极,早发现早改。

但如果机器上同时装着 17 和 21,而 Maven 自己跑在 17 上,光配release是不够的——release只是约束字节码目标,编译器本身还是 17 的 javac,用不了 21 的新语法。这时要用 toolchains:

<!-- ~/.m2/toolchains.xml --> <toolchains> <toolchain> <type>jdk</type> <provides> <version>21</version> <vendor>opensdk</vendor> </provides> <configuration> <jdkHome>/opt/jdk-21</jdkHome> </configuration> </toolchain> </toolchains>

version必须和 pom 里maven.compiler.release的值对得上,jdkHome指向解压目录(不是bin目录,这是高频错误)。配好之后还要在 pom 里显式启用 toolchains 插件,否则 Maven 不会去读这个文件:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <goals><goal>toolchain</goal></goals> </execution> </executions> <configuration> <toolchains> <jdk><version>21</version><vendor>opensdk</vendor></jdk> </toolchains> </configuration> </plugin>

vendor可以不写,写了更精确,适合一台机器上装了好几个不同来源的 21 的情况。这条配置生效后,mvn -version显示的仍然是 Maven 自身所在的 JVM,但 javac 用的是 toolchain 指定的那份——别被mvn -version的输出误导,那是常见的判断错误。

4.2 Gradle:toolchain 块加守护进程参数

Gradle 这边配置更集中,一个java块基本能覆盖:

// build.gradle java { toolchain { languageVersion = JavaLanguageVersion.of(21) vendor = JvmVendorSpec.matching(".*") } } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' options.compilerArgs << '-Xlint:all' << '-parameters' }

toolchain块的作用是让 Gradle 自己去寻找匹配的 JDK,找不到会直接报错并给出搜索路径,这个报错信息很有用,能反推它在哪些目录找过。vendor用正则匹配,写成.*表示不限来源,指定具体来源可以避免多个 21 之间的歧义。

真正容易翻车的是 Gradle 守护进程。守护进程是常驻的,它一旦用 17 启动,后续所有构建都复用这个 JVM,你在gradle.properties里怎么改 toolchain 都没用。控制它的参数写在同目录下:

# gradle.properties org.gradle.java.home=/opt/jdk-21 org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 org.gradle.caching=true

org.gradle.java.home指定守护进程自身用哪个 JDK 启动,和 toolchain 是两回事:前者管“Gradle 跑在哪个 JVM 上”,后者管“编译用哪个 JDK”。两个都指到 21 是最省心的组合。改完记得./gradlew --stop把旧守护进程杀掉,否则新配置不生效——这个“改了配置没重启守护进程”是构建工具里最经典的无效操作。

4.3 反向验证:用 javap 看字节码版本,别信 IDE

配完之后必须验证,而且不能只看 IDE 的 Project Structure 面板。IDE 显示的 JDK 只是它索引用的,真正编译的是构建工具。最硬的证据是 class 文件的 major version:21 对应 65,17 对应 61。

# 编译一个类,然后查它的字节码版本 mvn -q clean compile javap -v target/classes/com/example/Demo.class | grep 'major version' # Gradle 项目同理 ./gradlew -q clean compileJava javap -v build/classes/java/main/com/example/Demo.class | grep 'major version'

javap -v输出的是 class 文件的完整结构,包括常量池、方法表、版本号。major version: 65就是 21,61是 17,52是 8。这个数字骗不了人,比任何配置面板都可靠。

再补一道运行期验证:直接跑一次java -version加上你的应用启动命令,看启动日志里打印的 JVM 版本。有些项目会在启动时扫一遍System.getProperty("java.version")写进日志,这种日志在排查线上环境时价值极高——它记录的是真实运行的版本,而不是构建配置里声称的版本。我一般在 CI 里加一步java -XshowSettings:properties -version 2>&1 | grep java.version,把它当成构建产物的一部分存下来。

5. 避坑与排查:装了 21 还报 UnsupportedClassVersionError 的五种情况

这一类报错的信息量极大,但大多数人只看第一行就慌了。完整信息形如class file version 65.0, this version of the Java Runtime only recognizes class file versions up to 61.0,前半句是产物版本,后半句是运行环境版本,对照着看就能定位是编译侧高了还是运行侧低了。

5.1 编译期与运行期版本错位

现象一:本地编译通过,部署到服务器启动即报 UnsupportedClassVersionError,提示只支持到 61.0。

原因是构建产物用了 21 编译,而目标机器的运行时还是 17。这条链路上任何一环没对齐都会出现:CI 镜像里的 JDK 版本、打包脚本里写死的 JAVA_HOME、甚至某个基础镜像自带的运行时。

解决办法分两层。短期是把运行环境的 JDK 升到 21,把 OpenSDK 21 的解压目录同步到目标机器,改软链接和 JAVA_HOME。长期是在构建流程里加断言,让版本错位在构建阶段就暴露:

# 构建脚本里加一道版本闸门,低于 21 直接失败 required=21 current=$(java -XshowSettings:properties -version 2>&1 | awk -F'= ' '/java.specification.version/{print $2}' | tr -d ' ') if [ "${current%%.*}" -lt "$required" ]; then echo "需要 JDK $required,当前 $current" >&2 exit 1 fi

这段用-XshowSettings:properties拿到的是规范化版本号(形如21或17),比解析java -version的第一行稳。${current%%.*}是 shell 的参数展开,取第一个点之前的部分,处理21.0.1这种带小版本的情况。

现象二:同一个项目里,部分模块编译报“找不到符号”,另一些模块正常。

原因是多模块工程里各模块的maven.compiler.release不一致,父 pom 里配了 21,子模块自己又覆盖回 17。这种半新半旧的状态最麻烦,字节码版本混杂,运行时按最高的那个报错。

解决办法是把release只写在父 pom 的properties里,子模块一律不写。再加一步全量扫描,构建完成后检查所有 class 的版本号是否统一:

# 扫描所有 class 文件,列出非 65 的 find . -name '*.class' -path '*/target/classes/*' -print0 \ | xargs -0 -n1 sh -c 'v=$(javap -v "$0" 2>/dev/null | awk "/major version/{print \$3}"); [ "$v" != "65" ] && echo "$v $0"'

-print0配xargs -0是为了处理带空格的路径,这在 Windows 复制过来的目录里很常见。awk里用\$3取第三列,因为javap -v那行输出是major version: 65,第三列正好是数字。

5.2 IDE 与构建守护进程的缓存陷阱

现象三:命令行mvn clean compile一切正常,IDE 里点运行按钮却报版本不兼容。

原因是 IDE 的编译器是独立的,它用自己的索引和缓存,不读你的 shell 环境变量。IDEA 系的编译器设置藏在 Project SDK、Module SDK、Compiler target bytecode version 三个不同位置,任意一处是 17 都会出问题。

解决顺序是:先改 Project SDK 到 21 的解压目录,再确认各模块的 Language Level 跟在 21 上,最后检查Settings → Build → Compiler → Java Compiler里的 target bytecode version。改完执行一次Build → Rebuild Project,不要只点 Build——增量编译会保留旧 class,让你误以为改没生效。

现象四:Gradle 项目改了 toolchain,构建日志里还是显示旧版本 JDK 路径。

原因是守护进程还活着。Gradle 守护进程有生命周期,默认空闲三小时才退出,配置变更不会自动重启它。--stop是唯一可靠的清场手段。

# 杀掉所有守护进程,再重新构建 ./gradlew --stop ./gradlew build --no-daemon # 构建日志里确认收尾的 JVM 版本 ./gradlew -q javaToolchains

javaToolchains这个任务会把 Gradle 探测到的所有 JDK 列出来,包括路径、版本、来源,是排查“它到底用了哪个”的利器。--no-daemon用于一次性确认,确认完日常构建还是要开着守护进程,不然每次构建都重新加载模型,慢得让人怀疑人生。

5.3 jlink、证书与文件权限这三处容易翻车

现象五:jlink 裁剪时报“module not found”,或者裁剪出来的运行时启动就崩,提示缺少模块。

原因基本就三个:裁的时候没带全模块依赖、jmods目录缺失、或者用了--limit-modules把运行时需要的模块限制掉了。第一种最常见,手工列--add-modules时漏掉间接依赖。

解决办法是用jdeps反推依赖,而不是靠猜:

# 从应用 jar 反推它实际用到的模块 jdeps --print-module-deps --ignore-missing-deps --multi-release 21 app.jar # 把输出直接喂给 jlink jdeps --print-module-deps --ignore-missing-deps app.jar > modules.txt jlink --add-modules "$(cat modules.txt),jdk.crypto.ec" \ --strip-debug --no-man-pages --no-header-files \ --compress=zip-6 --output /opt/app-runtime

--ignore-missing-deps是针对第三方 jar 里有些类引用了不存在的依赖的情况,不加会直接失败。jdk.crypto.ec是我习惯额外加的,涉及 HTTPS 的椭圆曲线加密,jdeps 有时候分析不出来,漏了会在运行时握手失败——这类问题在本地 HTTP 环境下根本发现不了,一上生产就炸。

顺带说两个非代码类问题。一是证书:lib/security/cacerts是随包分发的,如果你之前手工往旧 JDK 的 cacerts 里导过私有 CA 证书,换到 21 之后必须重新导一遍,否则所有走内网 HTTPS 的请求全部握手失败。二是权限:tar解压后如果bin/*丢了执行位,java -version会提示找不到命令或权限不足,chmod +x bin/*一行解决,但排查时容易往环境变量方向跑偏。

提示:每次换 JDK 大版本,把证书重导、执行位检查、模块列表确认这三件事写进切换脚本,比事后回忆踩过的坑靠谱。

6. 进阶验证:用 jlink 裁一个最小运行时反查 21 是否完整

判断一份 OpenSDK 21 下载文件是否完整,最有效的手段不是看文件大小,而是拿它去做一次 jlink 裁剪。因为 jlink 会遍历jmods目录、解析模块依赖、生成新的运行时镜像,任何模块缺失或文件损坏都会在这一步暴露。这相当于给整个包做了一次全量体检,成本只有几十秒。

具体做法是先从一个真实应用反推模块集合,再裁出运行时,最后用裁出来的运行时跑一遍应用。三步都过,说明这份包是健康的。

# 第一步:反推模块依赖(--multi-release 要和应用的目标版本一致) jdeps --print-module-deps --ignore-missing-deps --multi-release 21 app.jar # 第二步:裁剪,注意 21 里 --compress 的写法变了 jlink --add-modules java.base,java.logging,java.sql,jdk.crypto.ec \ --strip-debug \ --no-header-files \ --no-man-pages \ --compress=zip-6 \ --output /opt/app-runtime # 第三步:用裁出来的运行时跑应用,跑通才算完 /opt/app-runtime/bin/java -jar app.jar

--compress在 21 里推荐写成zip-6这种新格式,老的--compress=2仍然接受但会打弃用告警,将来会被移除,新脚本直接按新写法来。zip-6里的数字是压缩级别,0 到 9,6 是体积和耗时的平衡点。--strip-debug去掉调试符号,--no-header-files去掉 C 头文件(跑 Java 应用完全用不上),--no-man-pages去掉手册页,这三个加起来通常能砍掉一半体积。

裁完之后对比一下量级:

运行时形态典型体积量级适用场景
完整 JDK(含 jmods 与工具)300MB 上下开发机、构建机
裁掉头文件与手册页的 JDK200MB 左右只需要编译、不需要文档的构建节点
按应用模块裁剪的运行时40–60MB容器镜像、边缘部署

体积这几个数字是量级参考,实际值取决于你带了哪些模块,java.base是必须的,java.desktop、java.xml这类一加上去体积涨得很明显,能不带就不带。容器镜像场景下这个差别很实在:基础镜像小了,拉取时间和存储占用都跟着降。

还有一个常被忽略的验证点:裁剪出来的运行时默认没有javac和jmods,所以它只能跑不能编。如果你的部署流程里还有“在目标机器上现场编译”的步骤,别用裁剪版,老老实实拷完整 JDK,或者在构建阶段就把产物打好。我见过有人把裁剪运行时当通用 JDK 发下去,结果运维想在机器上改一行代码重编,卡在那儿半天找不到 javac。

从那以后我每次拿到新的 OpenSDK 下载文件,都强制走一遍“校验哈希 → 绝对路径跑 java -version → jshell 试一段新语法 → jlink 裁一次”这套流程,四步加起来不到两分钟,却能挡住后续百分之八十的版本类故障。资源包里我习惯把校验清单和版本切换脚本放在一起,谁接手这台机器,照着跑一遍就知道环境是干净的。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询