写这篇文章的起因很简单——我受够了在 .bashrc 里维护一堆 JAVA_HOME_8、JAVA_HOME_11 的日子。每次接手一个老项目要切 Java 8,改完配置还得 source 一下,偶尔忘了重开终端,构建失败查半天才发现是版本没切过来。
如果你也经历过这些,这篇文章应该能帮到你。
先看看我原来的 .zshrc(macOS)是怎么管理多版本 JDK 的:
bash# JDK Config - 手动管理的噩梦
JAVA_HOME_8=/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home
JAVA_HOME_11=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
JAVA_HOME_17=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
export JAVA_HOME=$JAVA_HOME_8
alias jdk8="export JAVA_HOME=$JAVA_HOME_8 && java -version"
alias jdk11="export JAVA_HOME=$JAVA_HOME_11 && java -version"
alias jdk17="export JAVA_HOME=$JAVA_HOME_17 && java -version"
CLASS_PATH="$JAVA_HOME/lib"
PATH="$PATH:$JAVA_HOME/bin"
问题很明显:
后来我切换到 SDKMAN!,上面这些问题基本都解决了。
市面上管理 Java 版本的工具不少,我对比过几个主流方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 手动下载 + 环境变量 | 完全可控 | 繁琐,升级痛苦 |
| Homebrew (macOS) | 安装方便 | 多版本共存麻烦,cask 命名混乱 |
| apt/yum (Linux) | 系统集成 | 版本滞后,切换要用 update-alternatives |
| asdf | 多语言统一管理 | 插件质量参差不齐,Java 支持一般 |
| jEnv | 轻量 | 只管切换,不管安装 |
| SDKMAN! | 安装+切换一体,生态好 | 仅限 JVM 生态 |
最终选 SDKMAN! 的理由很简单:它专注于 JVM 生态,而且真的好用。一行命令装 Java,一行命令切版本,还能顺带管理 Maven、Gradle、Kotlin、Spring Boot CLI。对于主力写 Java 的开发者来说,这就够了。
SDKMAN! 的安装脚本需要 curl、zip、unzip,大多数系统都有,没有的话装一下:
bash# macOS(通常已经有了,没有就用 Homebrew)
brew install curl zip unzip
# Ubuntu / Debian
sudo apt update && sudo apt install -y curl zip unzip
# CentOS / RHEL
sudo yum install -y curl zip unzip
一行命令:
bashcurl -s "https://get.sdkman.io" | bash
装完之后,要么重开终端,要么手动加载一下:
bashsource "$HOME/.sdkman/bin/sdkman-init.sh"
验证安装:
bashsdk version
# 输出类似:SDKMAN! 5.18.2
装完后,SDKMAN! 的文件都在 ~/.sdkman/ 下:
~/.sdkman/ ├── bin/ # 可执行脚本 ├── candidates/ # 安装的工具(java、maven、gradle...) │ └── java/ │ ├── 17.0.11-tem/ # 具体版本 │ ├── 21.0.3-graal/ │ └── current -> 17.0.11-tem # 软链接,指向当前默认版本 ├── etc/ │ └── config # 配置文件 └── archives/ # 下载缓存
这个结构很清晰。current 是个软链接,sdk default 实际上就是改这个链接指向。
配置文件在 ~/.sdkman/etc/config,默认配置就能用,但有两项建议改一下:
bashvim ~/.sdkman/etc/config
properties# 安装时自动确认,省得每次按 Y sdkman_auto_answer=true # 进入目录时自动加载 .sdkmanrc(后面会讲这个功能,很有用) sdkman_auto_env=true
改完不用重启,下次执行 sdk 命令时自动生效。
先看看有哪些可选:
bashsdk list java
会列出一大堆发行版——Temurin(原 AdoptOpenJDK)、Amazon Corretto、GraalVM、Oracle、Zulu... 选择困难症可能会犯,我的建议是:
-tem),开源、稳定、更新及时-amzn),都有商业支持-graal)安装很简单,版本标识从 sdk list java 里复制:
bash# 安装 Java 17 (Temurin)
sdk install java 17.0.11-tem
# 安装 Java 21 (GraalVM)
sdk install java 21.0.3-graal
安装时会问你要不要设成默认版本,选 Y 就行。
两种切换方式,理解清楚区别:
bash# 临时切换(只在当前终端生效,关掉就没了)
sdk use java 21.0.3-graal
# 永久切换(改默认版本,新开的终端都用这个)
sdk default java 17.0.11-tem
我的习惯是:default 设成用得最多的版本(比如 17),临时需要用别的版本时用 use。
bash# 看当前用的是哪个版本
sdk current java
# 看所有工具的当前版本(如果你还装了 Maven、Gradle)
sdk current
# 看本地装了哪些 Java 版本
ls ~/.sdkman/candidates/java/
bashsdk uninstall java 11.0.20-tem
这是我觉得 SDKMAN! 最实用的功能之一。
场景:你同时维护三个项目——一个老项目用 Java 8,一个中期项目用 Java 11,新项目用 Java 21。每次切项目都要手动 sdk use 很烦。
解决方案:在项目根目录放一个 .sdkmanrc 文件。
bashcd ~/projects/legacy-system
sdk env init
会在当前目录生成 .sdkmanrc:
propertiesjava=8.0.412-tem
如果项目还用了特定版本的 Maven,可以手动加一行:
propertiesjava=8.0.412-tem maven=3.6.3
如果你之前按我说的配置了 sdkman_auto_env=true,那么 cd 进这个目录时会自动切换:
bash$ cd ~/projects/legacy-system
Using java version 8.0.412-tem in this shell.
$ cd ~/projects/new-system
Using java version 21.0.3-tem in this shell.
如果没开自动加载,手动执行 sdk env 也行。
把 .sdkmanrc 提交到 Git:
bashgit add .sdkmanrc
git commit -m "chore: pin Java version to 8 for legacy compatibility"
这样团队成员只要装了 SDKMAN!,clone 下来就能自动用对版本,不用再写文档说明"本项目请使用 Java 8"。
如果你之前和我一样是手动配置的,迁移过程分三步:装新的、改配置、删旧的。
先摸清楚你系统里装了哪些 JDK:
macOS:
bash# 系统目录
ls -la /Library/Java/JavaVirtualMachines/
# Homebrew 安装的
brew list | grep -i java
brew list | grep -i jdk
brew list | grep -i openjdk
Linux (Ubuntu/Debian):
bash# apt 安装的
dpkg --list | grep -i jdk
# 系统目录
ls -la /usr/lib/jvm/
# 手动装到 /opt 的
ls -la /opt/java/ 2>/dev/null
根据你原来用的版本,在 SDKMAN! 里装对应的:
bash# Temurin 在 Apple Silicon 上没有 Java 8
sdk install java 8.0.412-tem
# Azul Zulu 对 Apple Silicon 的 Java 8 支持最好
sdk install java 8.0.432-zulu
sdk install java 11.0.23-tem
sdk install java 17.0.11-tem
sdk install java 21.0.9-tem
# 设一个默认的
sdk default java 17.0.11-tem
验证一下:
bashwhich java
# 应该输出:/Users/你的用户名/.sdkman/candidates/java/current/bin/java
java -version
# 应该是你设的默认版本
打开你的 shell 配置文件(macOS 一般是 ~/.zshrc,Linux 一般是 ~/.bashrc):
bashvim ~/.zshrc # 或 ~/.bashrc
删掉所有手动配置的 Java 相关内容:
bash# 删掉这些 ↓
JAVA_HOME_8=/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home
JAVA_HOME_11=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
JAVA_HOME_17=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
export JAVA_HOME=$JAVA_HOME_8
alias jdk8="..."
alias jdk11="..."
alias jdk17="..."
CLASS_PATH="$JAVA_HOME/lib"
PATH="$PATH:$JAVA_HOME/bin"
保留 SDKMAN! 的初始化代码(通常在文件末尾,安装时自动加的):
bash# 这段保留 ↓
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
保存后重新加载:
bashsource ~/.zshrc
确认 SDKMAN! 工作正常后,删掉旧的 JDK:
macOS:
bash# 删除系统目录的 JDK
sudo rm -rf /Library/Java/JavaVirtualMachines/jdk-1.8.jdk
sudo rm -rf /Library/Java/JavaVirtualMachines/jdk-11.jdk
sudo rm -rf /Library/Java/JavaVirtualMachines/jdk-17.jdk
# 如果是 Homebrew 装的
brew uninstall --cask temurin
brew uninstall openjdk@17
# ... 其他版本
brew cleanup
Linux:
bash# apt 装的
sudo apt remove openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
sudo apt autoremove
# 手动装到 /opt 的
sudo rm -rf /opt/java/jdk-17
IntelliJ IDEA 里的 JDK 路径也要改一下:
File → Project Structure → SDKs
把旧的删掉,加新的:
~/.sdkman/candidates/java/17.0.11-tem ~/.sdkman/candidates/java/21.0.3-graal
或者直接用 current 软链接,这样 sdk default 切换后 IDE 会自动跟随:
~/.sdkman/candidates/java/current
如果你有一个手动下载的 JDK(比如 Oracle 官方版,需要登录下载的那种),可以注册到 SDKMAN! 里统一管理:
bashsdk install java oracle-21 /path/to/jdk-21
这样就能用 sdk use java oracle-21 来切换了。
SDKMAN! 下载的安装包会缓存在 ~/.sdkman/archives/,时间久了会占不少空间:
bash# 看看缓存占了多少
du -sh ~/.sdkman/archives/
# 清理
sdk flush archives
在没网的环境(比如某些内网服务器),可以开启离线模式:
bashsdk offline enable
当然,离线模式下没法安装新版本,只能用已经装好的。
SDKMAN! 不只能管 Java,整个 JVM 生态都能管:
bashsdk install maven 3.9.6 sdk install gradle 8.7 sdk install kotlin 1.9.23 sdk install springboot 3.2.5
用法完全一样。
我把常用的命令整理了一下,可以存个书签:
bash# ===== 版本安装 =====
sdk list java # 列出所有可装的版本
sdk install java 21.0.3-tem # 安装
sdk uninstall java 11.0.20-tem # 卸载
sdk install java mylocal /path/to # 注册本地版本
# ===== 版本切换 =====
sdk use java 17.0.11-tem # 临时切换(当前终端)
sdk default java 21.0.3-tem # 永久切换
# ===== 状态查看 =====
sdk current java # 当前 Java 版本
sdk current # 所有工具的当前版本
sdk version # SDKMAN! 自身版本
# ===== 项目配置 =====
sdk env init # 生成 .sdkmanrc
sdk env # 加载 .sdkmanrc
# ===== 维护 =====
sdk update # 刷新可用版本列表
sdk selfupdate # 升级 SDKMAN! 自身
sdk flush archives # 清理下载缓存
sdk offline enable # 开启离线模式
记录几个我遇到过的问题:
sdk: command not found新开的终端窗口里 sdk 命令不存在。通常是 shell 配置文件里的初始化代码没加载。检查 ~/.zshrc(或 ~/.bashrc)末尾有没有这段:
bashexport SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
IDE 有自己的 JDK 配置,不会跟着终端走。要么在 IDE 里手动改 SDK 路径,要么把 IDE 的 SDK 配置指向 ~/.sdkman/candidates/java/current。
公司网络可能有代理。在 ~/.bashrc 或 ~/.zshrc 里加上代理配置:
bashexport http_proxy=http://proxy.yourcompany.com:8080
export https_proxy=http://proxy.yourcompany.com:8080
从手动配置迁移到 SDKMAN! 之后,我的 .zshrc 里关于 Java 的配置从十几行缩减到了两行(就是 SDKMAN! 的初始化代码)。切版本从"改配置文件 → source → 验证"变成了一条命令。团队新人入职配环境从"看文档 → 下载 → 解压 → 配置 → 踩坑"变成了"装 SDKMAN! → 进项目目录 → 自动加载"。
工具的价值就在于此——把重复的事情自动化,把容易出错的事情标准化。
如果你还在手动管理 JDK 版本,不妨试试 SDKMAN!。