3226 字
约 10 分钟
8
S01-B02 · JDK 安装与多版本共存:JAVA_HOME 到底该指向谁

适用读者:已经装完 IDE、跑通 Hello World(S01-B01),但开始遇到"命令行和 IDE 用的不是同一个 JDK"的人。
前置:S01-B01。
实测环境:Ubuntu 24.04.1 LTS / OpenJDK 21.0.12.1(/usr/lib/jvm/java-21-openjdk-amd64)/ Apache Maven 3.8.7。
版本基线:JDK 17 与 21 双装;IDEA 2025.x。
覆盖旧文:A-1373(35 个原文点位)。
新增扩展:E-S01-001(发行版对比)、E-S01-002(sdkman / mise)、E-S01-003(JAVA_HOME 与 PATH)、E-S01-008(SDK / Language Level / target 对齐)。
上一篇:S01-B01 · 开发环境全景:JDK、Maven、IDE、终端各自管什么
下一篇:S01-B03 · Maven 安装、镜像与第一次构建。


1. 一句话结论

java 命令靠 PATH 找,构建工具和 IDE 常常靠 JAVA_HOME 找,IDE 项目又单独存了一份 SDK 设置。三套机制各自独立——所以"三处版本一致"必须逐处验证,不能靠感觉。

本篇要解决的就是这个"三处对齐"问题:

位置 由谁决定 常见失控原因
① 命令行 java / javac PATH 里第一个 java(常是 /usr/bin/java → 符号链接) PATH 顺序变了、装新 JDK 后没切 alternatives
② Maven / Gradle 运行用的 JDK JAVA_HOME,缺失时回退到 PATH JAVA_HOME 空(能跑但要警告)、指向 JRE、指向旧版本
③ IDE 项目用的 JDK 项目 .idea/ 里的 SDK 名 + IDE 全局 SDK 表 IDE 内置 JDK、SDK 名指向已删除目录

2. 先把概念分清:别把四个词当一件事

词 是什么 你要关心吗
JVM 执行字节码的虚拟机 不用直接管,跟着 JDK 来
JRE JVM + 核心类库,只能运行不能编译 不要把 JAVA_HOME 指向它(javac 会找不到)
JDK JRE + 编译器(javac)+ 工具(jstack、jmap、jshell…) 这才是要装的东西
发行版(Distribution) 同一个 JDK 版本的不同"打包商":Temurin、Corretto、Zulu、Microsoft Build、Oracle 装哪个都行,但别混着装;生产环境统一一个

版本号怎么读

openjdk version "21.0.12.1" 2026-08-18
        ↑        ↑  ↑  ↑
        │        │  │  └── 安全补丁号(build 内的小修订)
        │        │  └───── 补丁版本(含安全修复)
        │        └──────── 特性版本(21 = LTS)
        └───────────────── 发行版前缀;Temurin 会显示 "Temurin"
  • LTS:8 / 11 / 17 / 21 / 25。团队项目锁 LTS,别用中间版本(如 22、23)做长期环境。
  • UnsupportedClassVersionError: class file version 65.0 的含义:这个 .class 是 JDK 21 编译的(65),而运行它的 JVM 是 17(61)。错误在"运行端 JDK 太旧",不在代码。

3. 实测:JAVA_HOME 空着也能跑,指错了立刻死

这是本篇最值得记住的一组实验(同一台 Ubuntu 24.04.1,只改变量,不改系统):

# ① 不设 JAVA_HOME(该机器默认状态)
$ mvn -v
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 21.0.12.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64

能跑。 Maven 的启动脚本在 JAVA_HOME 为空时会回退到 PATH 里的 java。

# ② JAVA_HOME 指向一个不存在的目录
$ JAVA_HOME=/opt/does-not-exist mvn -v
The JAVA_HOME environment variable is not defined correctly,
this environment variable is needed to run this program.

直接失败,而且报错信息不会告诉你"你指的是个不存在的目录"。

# ③ JAVA_HOME 指向真实 JDK
$ JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 mvn -v
Apache Maven 3.8.7
Java version: 21.0.12.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64

结论

  1. JAVA_HOME 为空 ≠ 环境坏,很多工具能靠 PATH 兜底;但兜底是"隐式"的,一旦你装了第二个 JDK,兜底结果就不可预测。
  2. JAVA_HOME 指错 = 直接崩,且报错不指向根因。
  3. 所以实践上:要么明确设置 JAVA_HOME,要么彻底依赖 alternatives/sdkman 统一管理——不要半吊子。

4. 看一眼链接链:java 到底是谁

$ which java
/usr/bin/java

$ readlink -f $(which java)
/usr/lib/jvm/java-21-openjdk-amd64/bin/java

# Debian/Ubuntu 的版本登记表(只读)
$ update-alternatives --display java
java - auto mode
  link best version is /usr/lib/jvm/java-21-openjdk-amd64/bin/java
  link currently points to /usr/lib/jvm/java-21-openjdk-amd64/bin/java
  link java is /usr/bin/java
/usr/lib/jvm/java-21-openjdk-amd64/bin/java - priority 2111

/usr/bin/java 是符号链接,指向系统登记(alternatives)里选中的那个 JDK。装第二个 JDK 后,update-alternatives 里会出现两条,切哪个就生效哪个。

这就是为什么"卸载/重装 JDK 后 java -version 变了,但 mvn 没变":前者走 alternatives 的链接,后者走 JAVA_HOME。两条路。


5. 装两套 JDK:17 与 21

5.1 Ubuntu / Debian(alternatives 体系)

sudo apt update
sudo apt install -y openjdk-17-jdk openjdk-21-jdk

# 查看已安装
ls /usr/lib/jvm/
# java-17-openjdk-amd64  java-21-openjdk-amd64

# 交互切换(java 与 javac 要分别切!)
sudo update-alternatives --config java
sudo update-alternatives --config javac

验证:

$ java -version && javac -version
openjdk version "17.0.x" ...
openjdk 17.0.x ...

坑:只切 java 不切 javac,会出现 java -version 是 17、javac -version 是 21 —— 编译用 21、运行用 17,直接命中 §2 的 UnsupportedClassVersionError。两者必须一起切。

5.2 macOS(Homebrew 或安装包)

# Homebrew(注意 Homebrew 只装 JDK,不自动改 JAVA_HOME)
brew install --cask temurin@17 temurin@21

# 找到安装位置
/usr/libexec/java_home -V
# Matching Java Virtual Machines:
#   21.0.x (arm64) "Eclipse Adoptium" - "Temurin-21" /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
#   17.0.x (arm64) "Eclipse Adoptium" - "Temurin-17" /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

# 临时切到 17(只对当前 shell 生效,最安全)
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

/usr/libexec/java_home -v <版本> 是 macOS 最省事的切换方式:不改系统状态,只影响当前会话。

5.3 Windows

  • 官方 .msi 安装包默认写入注册表,但不会替你改 JAVA_HOME。
  • 双版本共存建议:安装到不同目录(如 D:\tools\jdk17、D:\tools\jdk21),把 JAVA_HOME 指向"当前使用"的那个,Path 里只放 %JAVA_HOME%\bin(不要同时放两个 JDK 的 bin,否则先命中谁全看顺序)。
  • 切换靠改环境变量或写两个 .bat:
:: use-jdk17.bat
setx JAVA_HOME "D:\tools\jdk17"
:: 需要重开终端;PATH 里已引用 %JAVA_HOME%\bin

坑:JAVA_HOME 的值不要带引号,也不要带 \bin 后缀。正确:D:\tools\jdk17;错误:"D:\tools\jdk17" 或 D:\tools\jdk17\bin。


6. 三种切换方案对比:选一种,别混用

方案 平台 优点 缺点 适合谁
update-alternatives Debian/Ubuntu 系统级、GUI/脚本都生效;一条命令切换 只覆盖系统包装的 JDK;要记得切 javac Linux 服务器、固定几套系统 JDK
sdkman Linux/macOS/WSL 任意发行版、任意版本;sdk use 只影响当前 shell;可装 Temurin/Corretto/Zulu 需要 source 初始化脚本;CI 里要显式安装 开发者本机(推荐)
mise / asdf 跨平台(含 Windows) 与 Node/Python 等统一管理;.tool-versions 随仓库走 配置概念略多 多语言项目、要"项目级锁版本"
手工环境变量 全平台 零依赖 容易忘、容易指错(§3 实验②) Windows、临时验证
IDE 内置下载器 全平台 一键 与命令行脱钩,制造"只在 IDE 能跑" 不建议

sdkman 最小用法

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

sdk list java | head            # 列出可用发行版/版本
sdk install java 21.0.5-tem     # 装 Temurin 21
sdk install java 17.0.13-tem    # 装 Temurin 17
sdk use java 17.0.13-tem        # 只对当前 shell 生效(推荐)
sdk default java 21.0.5-tem     # 改默认

关键差别:sdk use 只影响当前终端会话,sdk default 才改全局。这样你可以"这个窗口跑老项目(17),那个窗口跑新项目(21)",互不干扰。


7. 在 IDEA 里配置 JDK:项目级与全局

旧文 A-1373 给的步骤是对的,这里补上"为什么"和"配完怎么验证"。

7.1 为当前项目配置

File → Project Structure…(Ctrl+Alt+Shift+S / ⌘;)
  → Project Settings → Project
      SDK:            选择 JDK 17 或 21(没有就 Add SDK → JDK… 指向安装目录)
      Language level: 与 SDK 主版本一致(17 / 21)
  → Project Settings → Modules → Sources
      Language level: 同上
  → Apply → OK

7.2 全局默认(影响以后新建的项目)

File → New Projects Setup → Structure…
  → 同上配置 → 以后 New Project 默认使用

7.3 三者对齐:SDK / Language level / Compiler target

这是 A-1373 没讲、但最容易出错的一层。"我明明换了 JDK 21,为什么还是编译成 17 的字节码?" 因为有三处设置:

设置 作用 不对齐的症状
Project SDK 用哪个 JDK 编译/运行 无法用新语法(如 record pattern)
Language level IDE 允许你用哪版语言特性 语法报红说"不支持"
Maven maven.compiler.release(或 source/target) 实际产出什么字节码 编译成功但运行报 UnsupportedClassVersionError

Maven 项目里,最终话语权在 pom.xml:

<properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

release 比 source+target 更好:它同时约束语言特性与 API(用 source/target 时你可能调用了 JDK 21 才有的 API 却声称兼容 17)。

实测提醒(S01-B03 复现):只写 <maven.compiler.release> 属性不一定生效——release 是 maven-compiler-plugin 3.6+ 才支持的参数。Maven 3.8.7 在不声明插件版本的默认插件链里用的是 3.1,会直接报:

[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.

所以生产上要显式锁插件版本(maven-compiler-plugin 3.13.0 起)+ <release>,两者缺一不可。完整实验见 S01-B03 §5。

验证方式(命令行,不靠肉眼):

$ mvn -q help:evaluate -Dexpression=maven.compiler.release -DforceStdout
21

$ javap -v target/classes/Main.class | grep 'major version'
  major version: 65        # 65 = Java 21;61 = Java 17

major version 对照:52=8、55=11、61=17、65=21、69=25。

7.4 IDE 与 Maven 不同步的典型现象

现象 原因 处置
项目全红,但终端 mvn -q compile 成功 IDE 的 Maven 模型未 reload Maven 面板 → Reload All Maven Projects
IDE 里 JDK 是 21,mvn 出来 17 IDE SDK ≠ JAVA_HOME 统一到同一个 JDK(§3 实验③)
换了 pom.xml 里的 release 但 IDE 仍报语法错 IDE 的 Language level 跟着 pom 走需要 reload reload 后检查 Project Structure → Modules

8. 六个高频坑(按踩坑概率排序)

# 坑 症状 修法
1 JAVA_HOME 指向 .../jre javac: not found;Maven 报环境不正确 指到 JDK 根目录(含 bin/javac)
2 只切 java,没切 javac 编译/运行版本错位 两个都 update-alternatives --config
3 Windows Path 里并存两个 JDK bin 版本随机 只留 %JAVA_HOME%\bin
4 JAVA_HOME 带引号或带 \bin Maven 报 "not defined correctly" 去掉引号与后缀
5 用 IDE 内置下载器装 JDK 只在 IDE 能跑;命令行找不到 自己装,IDE 里 Add SDK 指过去
6 装了非 LTS(22/23)当日常环境 半年后依赖/插件不支持 回归 17/21

9. 自检清单

按顺序执行,全部符合才算"三处对齐":

# ① 命令行
java -version        # 期望:17.x 或 21.x
javac -version       # 期望:与上面同主版本

# ② JAVA_HOME
echo $JAVA_HOME      # 期望:非空,且指向 JDK 根(不是 jre,不带 /bin)
ls $JAVA_HOME/bin/javac   # 期望:文件存在

# ③ Maven
mvn -v | head -3     # 期望:Java version 与 ① 一致

# ④ 字节码
javap -v target/classes/*.class | grep 'major version'   # 期望:与 release 一致

IDE 侧两条:Project Structure → Project SDK 与命令行同版本;Language level 与 maven.compiler.release 同代。


10. 旧文对照

项 旧文 A-1373 本篇处理
项目级配置 JDK ✅ 给了 File → Project Structure → SDKs → + → Add JDK 步骤 保留,补 Language level 与 pom 的 release
全局配置 JDK ✅ File → New Projects Setup → Structure 保留,补"为什么新建项目会自动继承"
前提(先装 JDK 并配环境变量) ⚠️ 只给了外链到两篇 JDK 教程 升级为本篇 §5 正文,不再依赖外链
JAVA_HOME 的行为 ❌ 未提及 新增 §3 三组实测
多版本共存 ❌ 未提及 新增 §5、§6(alternatives / sdkman / mise 对比)
SDK / Language level / release 三者关系 ❌ 未提及 新增 §7.3,附 major version 对照表

11. 自测

  1. java -version 是 21,mvn -v 显示 17。列出至少两种可能原因与各自的修法。
  2. 为什么建议 maven.compiler.release 而不是 source+target?
  3. update-alternatives --config java 之后还需要做什么?不做会怎样?
  4. macOS 上 export JAVA_HOME=$(/usr/libexec/java_home -v 17) 与 sdk default java 17 的差别是什么?
  5. 团队里有人 Windows 装了两套 JDK,Path 里都放了 bin。他报"版本随机变",你怎么一句话定位?

12. 下一篇

S01-B03 · Maven 安装、镜像与第一次构建

JDK 对齐之后,第一个会挡路的就是依赖下载:Could not resolve dependencies 到底该查本地仓库、镜像配置还是网络?下一篇用"空 ~/.m2 首次构建"和"改镜像再构建"两组对照,把 Maven 的查找顺序讲清楚,并给出 mvnw 的可复现做法。


本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-1373;扩展点 E-S01-001/002/003/008。点位映射见 ../coverage.csv。

S01-B02 · JDK 安装与多版本共存:JAVA_HOME 到底该指向谁
https://www.clxhxhhr.top/posts/5156/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。