如果你是个常年跟 Java 打交道的人,一定经历过这种场景:电脑里同时装了 JDK 8、JDK 11、JDK 17,甚至还有几个不同厂商的发行版,项目 A 要 JDK 8 编译,项目 B 强制要求 JDK 17,每次切换都得手动改 JAVA_HOME、改 PATH,改完还要在终端里反复确认 java -version,一个不小心就把全局环境搞崩。项目 SDKMAN(Software Development Kit Manager)就是专门解决这个痛点的命令行工具,它能在一台机器上同时安装、管理、切换多个版本的 JDK、Maven、Gradle、Spring Boot 等开发套件,让 Java 环境管理从“手工改配置”变成“一行命令搞定”。本文适合所有被 Java 环境折腾过的开发者,不管你是刚入门的新手,还是被多版本切换折磨已久的资深工程师,这篇文章都能帮你把 SDKMAN 真正用起来。
1. 项目整体设计与核心思路
1.1 为什么不用手动配置,非要选 SDKMAN
先聊聊我在接触 SDKMAN 之前的状态。早期配 Java 环境,标准的流程是去 Oracle 官网或者 Adoptium 下载安装包,双击安装,然后手动配置 JAVA_HOME 和 PATH。听起来不难,但一旦涉及到版本切换,问题就来了。
假设你按网上的教程把 JAVA_HOME 指向了 JDK 8 的安装目录,过两天要玩 Spring Boot 3 需要 JDK 17,你就得再次修改系统环境变量,把 JAVA_HOME 改成 JDK 17 的路径,还要把 PATH 里原来指向 JDK 8 bin 目录的条目改成新的。改完之后,打开一个新的终端窗口,发现某些 IDE 还是认旧版本,因为 IDE 启动时保存了环境变量快照,你得重启 IDE。这还只是单机单用户的情况,要是你在公司服务器上部署、或者维护多台开发机,这种手动方式简直就是灾难。
与此同时,你用包管理器(比如 Homebrew、apt)也能装 Java,但包管理器通常只维护一个默认版本,装多个版本时切换依然要靠 update-alternatives 之类的机制,操作起来并不直观,而且对于 Maven、Gradle、Spring Boot 这类同样需要多版本管理的工具,包管理器基本帮不上忙。
SDKMAN 的设计思路很巧妙:它把所有的 SDK 工具统一收拢到一个 shell 脚本体系里,通过一个专门的目录(默认是 ~/.sdkman)管理所有已安装的版本。每次执行 sdk use 或者 sdk default,它本质上是去修改当前 shell 会话的环境变量,并把 JAVA_HOME 指向对应的安装目录。这个过程是用户态的、隔离的,你不需要动系统级的 /etc/profile,也不需要在多个配置文件里手动改路径,更不会因为切换环境导致整个系统崩溃。
深层一点说,SDKMAN 解决的问题不是“怎么装一个 Java”,而是“怎么让多个 Java 版本和你当前的工作流和平共处”。它把版本选择的逻辑从“操作系统级”下沉到了“用户级”,让每个开发者都能独立控制自己的环境,这在团队协作和 CI/CD 场景里价值尤其大。
1.2 SDKMAN 的核心优势与适用场景
SDKMAN 不是一个新东西,它最早叫 GVM(Groovy enVironment Manager),后来改名并扩展成 SDKMAN,现在由 Gradle 社区的人维护。我用下来,它相比手动配置和包管理器有几个非常明显的优势:
- 多版本并行安装:一条命令安装一个新版本,多个 JDK 共存互不干扰。
- 全局/局部切换分离:
sdk use只影响当前终端,sdk default影响全局,灵活度很高。 - 自动配置 JAVA_HOME:切换版本时自动设置
JAVA_HOME和PATH,不用手动改任何配置文件。 - 自带版本列表查询:想知道某个工具有哪些版本可用,一条命令就能查得清清楚楚。
- 非 Java 工具也能管理:Maven、Gradle、Ant、Spring Boot CLI、Kotlin、Scala 等都在它的管理范围内,生态很完整。
那它适合什么场景?我总结了几类:
- 日常开发多项目并行:微服务项目用 JDK 8,新项目用 JDK 17,切换零成本。
- 学习与实验:想试试 JDK 21 的新特性,又不想动生产环境,装个新版本随便玩。
- 教学培训:给学员准备统一的开发环境,不用每个人手动折腾系统变量。
- CI/CD 流水线脚本:在构建脚本里用 SDKMAN 安装指定版本的 JDK,保证构建环境一致。
当然,它也有不适合的场景。比如 Windows 原生环境(没有 WSL 或 Git Bash 的情况下)不太好用;比如你对系统管理有强制要求,必须用公司的统一镜像部署 JDK;再比如你只需要单一版本、永远不换版本,那确实没必要引入额外的工具。技术选型永远是看需求,不是看工具多炫酷。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与 SDKMAN 安装
2.1 安装前置条件:Unix、bash、curl
SDKMAN 的安装脚本本身非常轻量,它对系统有一个基本要求:必须运行在 Unix-like 的环境里,包括 macOS、Linux、Windows Subsystem for Linux(WSL)、Cygwin、Git Bash 等。如果你的工作环境是 Windows 原生 PowerShell,那 SDKMAN 官方并不原生支持,但可以通过 WSL 绕过去,或者直接用 Chocolatey 装 JDK(但这就失去了 SDKMAN 的核心价值,下文会细说)。
另外,安装过程需要用到 bash、curl 和 unzip。实际上大多数类 Unix 系统都自带这些工具,但有些精简版的 Linux 发行版可能缺 unzip,建议先用下面命令确认:
bash复制bash --version
curl --version
unzip -v
如果某个命令提示 not found,就先装一下。以 Ubuntu/Debian 为例:
bash复制sudo apt update
sudo apt install curl unzip zip -y
macOS 用户一般自带 curl,unzip 也有,但有些新系统默认 zsh,执行安装脚本时可能需要切换成 bash,这个细节下面会提到。
2.2 官方安装步骤与验证
SDKMAN 官方给出的安装命令非常简洁,一条 curl 管道到 bash 就能装完。在终端里执行:
bash复制curl -s "https://get.sdkman.io" | bash
安装脚本执行之后,会在终端里输出一堆日志信息,告诉你安装到了 ~/.sdkman,然后提示你需要 source "$HOME/.sdkman/bin/sdkman-init.sh" 才能立即使用。如果你当前终端是 bash,也可以重新打开一个新的终端窗口,SDKMAN 会通过它自己写入的初始化脚本自动加载。
这里有个特别容易踩的坑:安装完成之后,如果你直接敲 sdk version 报 command not found,大概率是没有 source 初始化脚本。SDKMAN 的安装过程会往你的 shell 配置文件(如 ~/.bashrc、~/.zshrc)里追加一段内容,但当前已经打开的终端会话不会自动生效。所以要么新开一个终端,要么手动执行:
bash复制source "$HOME/.sdkman/bin/sdkman-init.sh"
验证安装是否成功:
bash复制sdk version
正常情况下你会看到类似这样的输出:
bash复制SDKMAN 5.18.2
这个版本号就是 SDKMAN 自身的版本。看到之后说明安装成功,可以开始用了。
如果你用的是 Windows 环境,我个人推荐两条路线:
- 装 WSL2 + Ubuntu,然后在 WSL 里安装 SDKMAN。这是最接近原生 Linux 体验的方式,开发 Java 项目也没有兼容性问题。
- 用 Git Bash。SDKMAN 官方支持在 Git Bash 里运行,但要注意路径转换等小问题,体验上略逊于 WSL。
2.3 配置代理与离线模式
部分公司网络环境访问 GitHub 或其他境外源会比较慢,SDKMAN 在安装 SDK 时默认从厂商的 API 和下载地址拉取,这就可能导致下载超时。SDKMAN 本身支持 HTTP 代理配置,方式是修改 ~/.sdkman/etc/config 文件:
bash复制sdkman_curl_connect_timeout=20
sdkman_curl_max_time=0
sdkman_curl_retry_max=3
sdkman_curl_retry_delay=20000
如果需要走代理下载,可以直接在命令行里设置环境变量:
bash复制export http_proxy=http://your-proxy:8080
export https_proxy=http://your-proxy:8080
然后再执行 sdk install。SDKMAN 底层用的是 curl,所以它会读取这些标准代理变量。
另外,如果你已经在本地通过其他方式下载了某个 JDK 的压缩包,SDKMAN 支持“离线”方式安装,也就是手动把压缩包放到 ~/.sdkman/archives/ 目录下,文件名要符合 SDKMAN 的命名规则,然后再执行 sdk install。这个技巧在服务器网络受限时非常有用,我后面实操章节会详细演示。
3. 核心操作:用 SDKMAN 管理 Java 环境
3.1 查看可用版本并安装 JDK
SDKMAN 里把一个可管理的工具统称为 Candidate,比如 Java、Maven、Gradle 各自都是候选工具。查看 Java 有哪些可用版本:
bash复制sdk list java
这个命令会输出一张很长的表格,里面列出了当前所有可供安装的 Java 发行版。注意,它不只是 Oracle JDK 和 OpenJDK,还包括 Temurin(Adoptium)、Zulu(Azul)、Liberica、Amazon Corretto、GraalVM 等。每个发行版都会有一个独特的标识符,比如:
bash复制Temurin | 21.0.2-tem | | installed | 21.0.2-tem
这个标识符就是安装时用的版本号。安装指定版本:
bash复制sdk install java 21.0.2-tem
如果你想安装某个发布商的最新版,可以不指定具体版本号,SDKMAN 会自动选择该发布商的最新版:
bash复制sdk install java 21-tem
个人建议在初学阶段尽量用带完整版本号的命令,方便精确管理。等熟悉了之后再偷懒用缩写。安装过程会先下载压缩包,然后解压到 ~/.sdkman/candidates/java/ 目录下,并且自动切换为当前会话的默认版本。
有一个小细节:安装时终端会问你是否要把这个版本设为 default,输入 y 或者 n 就行。如果你直接回车,默认是 n。
3.2 版本切换三种姿势:use、default、shell
SDKMAN 切换 Java 版本的命令看起来很简单,但很多新手分不清 use 和 default 的区别,这里重点强调一下。
sdk use java 17.0.10-tem:仅在当前终端会话里临时切换。关闭这个终端之后再打开,Java 版本会回到原来的 default。适合“我只是在这个窗口里测一下这个版本”的场景。
sdk default java 17.0.10-tem:修改全局默认版本,对当前会话以及以后所有新开的终端都生效。适合“我决定以后都用这个版本”的场景。
还有一个 sdk shell java 17.0.10-tem,这个是在当前 shell 里设置一个临时变量,但对当前会话的影响范围更小,只影响子进程。一般情况下我用得不多,但如果你在写脚本,临时需要覆盖版本,这个命令就会很顺手。
我举个具体例子。假设你默认版本是 JDK 8,现在临时要在一个终端里测试某个项目是否兼容 JDK 17,那么你只需要:
bash复制sdk use java 17.0.10-tem
java -version
测试完毕后关掉终端即可,系统里的默认配置完全没被污染。这个机制本质上是因为 SDKMAN 通过修改当前 shell 的 JAVA_HOME、PATH 等环境变量来实现“会话级隔离”,所以不会像系统级配置那样“改了就回不去”。
3.3 验证当前 Java 环境
安装和切换完成后,验证环境是否生效是必须做的一步。先看看 SDKMAN 当前的候选工具版本:
bash复制sdk current
输出示例:
bash复制Using java version 17.0.10-tem
Using maven version 3.9.6
再看看 Java 本身:
bash复制java -version
which java
echo $JAVA_HOME
注意,这些命令必须要在同一个终端窗口里执行。如果你新开一个终端,SDKMAN 会自动加载 default 配置,显示的还是 default 版本。
我建议把这些验证命令绑成一个常用组合,每次配好环境就敲一遍,省心省力:
bash复制sdk current && java -version && echo "JAVA_HOME=$JAVA_HOME"
3.4 卸载与清理本地 JDK
用了 SDKMAN 之后,卸载一个 JDK 版本变得非常简单:
bash复制sdk uninstall java 17.0.10-tem
执行后会问你是否确认删除,输入 y 即可。它会直接删除 ~/.sdkman/candidates/java/17.0.10-tem 这个目录,不会在系统里留下乱七八糟的残留文件。
这里就回答了开头搜索热词里那个问题:“java软件删除的时候配置的环境需要删吗”。如果你用的是传统手动安装方式(比如安装包安装、或者解压到自定义目录),卸载软件之后确实需要手动清理环境变量。因为软件本体删除后,JAVA_HOME 和 PATH 里指向的路径就失效了,轻则 java -version 报错,重则某些脚本无法正常执行。你需要在系统环境变量面板里把对应的 JAVA_HOME 条目删除,同时把 PATH 里指向旧版本 bin 目录的条目也删掉。这个过程很繁琐,而且容易漏删。
但如果你的 Java 环境是通过 SDKMAN 管理的,那就不存在这个问题。SDKMAN 的所有环境变量都是在初始化脚本里动态生成的,它永远指向当前 candidates 目录下的具体路径。你卸载某个版本之后,SDKMAN 会自动回退到其他已安装版本,或者提示没有可用的版本,根本不需要你去手动编辑系统级配置。这就是“工具型管理”和“手工配置”的本质差别。
4. 进阶实操:Maven、Gradle 与多项目环境
4.1 安装并联动管理 Maven/Gradle
SDKMAN 不止管 Java,还能管 Java 生态里的构建工具。我日常用得最多的就是 Maven 和 Gradle。
安装 Maven 和 Gradle 的方式跟安装 JDK 几乎一模一样:
bash复制sdk install maven
sdk install gradle
如果不指定版本,SDKMAN 会安装当前默认的稳定版。如果你想知道所有可用的 Maven 版本,执行 sdk list maven 即可。
为什么构建工具也要纳入 SDKMAN 管理?因为不同的项目对 Maven 和 Gradle 版本也有要求。比如某些老项目用的还是 Maven 3.6,而新项目要求 Maven 3.9;Gradle 更是如此,老版本 6.x 和 8.x 的兼容性天差地别。如果手动管理这些版本,那真的是一场噩梦。但用 SDKMAN,你可以随时切换:
bash复制sdk default maven 3.9.6
sdk use gradle 8.5.0
更妙的是,SDKMAN 在切换 Java 版本的时候不会影响 Maven/Gradle 的版本配置,它们是独立的候选工具,互不干扰。当然,Maven 和 Gradle 运行时依赖 JAVA_HOME,如果你切换了 Java 版本,Maven 跑的时候就会用新版本的 Java,这个联动关系是符合预期的。
4.2 按项目自动切换版本:.sdkmanrc
如果你同时维护多个项目,每个项目用的 Java 版本不同,每次都手动敲 sdk use 还是有点麻烦。SDKMAN 提供了一个更聪明的方案:在每个项目根目录下放一个 .sdkmanrc 文件,然后在 SDKMAN 的配置文件 ~/.sdkman/etc/config 里开启 sdkman_auto_env=true,之后你 cd 进项目目录时,SDKMAN 会自动读取这个文件并切换 Java 版本。
.sdkmanrc 的内容格式很简单:
bash复制java=17.0.10-tem
maven=3.9.6
生成这个文件的命令是:
bash复制sdk env init
它会根据你当前正在使用的版本生成一个 .sdkmanrc 文件。之后当你进入这个目录时,SDKMAN 会提示:
bash复制Using java version 17.0.10-tem in this shell.
这个功能真的非常实用,尤其是应对多项目并行开发的时候,再也不用记“这个项目用哪个 JDK”了,项目目录本身就带着答案。有一点要注意:.sdkmanrc 应该提交到 Git 仓库里,这样团队里其他成员 clone 项目后也能自动使用相同的版本配置,减少“我本地能跑,你本地不能跑”的环境不一致问题。
4.3 自定义安装本地 JDK 并纳入 SDKMAN
有些团队的 JDK 是公司内部的定制版,或者你需要用 SDKMAN 列表里没有的发行版,这种情况下 SDKMAN 也提供了“本地版本接入”机制。
思路很简单:把本地已有的 JDK 压缩包放到 ~/.sdkman/archives/ 目录下,然后执行:
bash复制sdk install java <自定义版本标识> <本地文件路径>
比如:
bash复制sdk install java my-custom-jdk ~/Downloads/jdk-custom.tar.gz
执行之后,SDKMAN 会把这个 JDK 解压到 candidates 目录下,注册为一个新的版本。注意版本标识需要遵循 SDKMAN 的命名规范,一般是 版本号-发行商缩写,比如 17.0.10-custom。
如果你已经有一个解压好的 JDK 目录,不想再打包成压缩包,也可以直接把目录复制到 ~/.sdkman/candidates/java/ 下,命名为你想要的版本标识,然后重新打开终端,SDKMAN 就会识别出这个版本。但这种方式需要保证目录内部结构符合 SDKMAN 预期(即有 bin/java 这样的可执行文件),否则切换过去之后 java -version 会报错。
5. 常见问题与排查技巧实录
5.1 执行 sdk 命令提示 command not found
这个问题的原因我在前面提到过,绝大多数情况是当前终端没有加载 SDKMAN 的初始化脚本。解决办法:
bash复制source "$HOME/.sdkman/bin/sdkman-init.sh"
如果你新开终端还是不行,那就要检查 shell 配置。SDKMAN 安装脚本会在 ~/.bashrc 或 ~/.zshrc 末尾追加一行:
bash复制#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
如果你手动编辑过配置文件,不小心把这行删了,或者某些优化脚本把文件覆盖了,就会导致 SDKMAN 失效。检查并补回即可。
还有一个容易被忽略的点:在 macOS 上,如果你用的 shell 是 zsh,而 SDKMAN 安装时写入了 ~/.bashrc,那新终端不会加载它。解决办法是手动把上面这行内容追加到 ~/.zshrc。虽然 SDKMAN 安装脚本通常会检测当前 shell 类型,但有些第三方终端模拟器可能不会触发正确的检测逻辑。
5.2 sdk install 下载慢或失败
如果安装某个版本时卡在下载阶段,或者直接报网络错误,通常有这几个原因:
- 网络环境无法访问国外 CDN。SDKMAN 默认的下载源是各发行商的官网或 GitHub Releases,国内网络可能不稳定。两个思路:一是通过代理,二是改用国内镜像。代理方式上文已经提过,镜像方式比较复杂,因为 SDKMAN 的下载地址是写在各个候选工具的数据源里的,你只能通过改 host 或者其他网络层手段优化。
- 本地磁盘空间不足。SDKMAN 安装 JDK 时会把压缩包缓存到
~/.sdkman/archives/,然后解压到~/.sdkman/candidates/。单次安装 JDK 压缩包通常是 100-200MB,解压后占用约 300MB 左右。如果根目录空间不够,解压会失败。 - 代理环境变量未生效。我遇到过一种情况:在
~/.bashrc里设置了http_proxy,但 SDKMAN 执行时用的是 curl,curl 默认会读取这个变量,但如果你设置了no_proxy并且把 SDKMAN 的域名排除在外,也会导致走不了代理。排查时可以直接用curl -v测试下载地址,看看请求有没有真正走到代理。
5.3 切换版本后 Java 版本没变
这个问题通常是“你以为你切了,但其实切换没有生效”。分两种场景:
场景一:你用了 sdk use,但终端里 java -version 还是旧的。这大概率是你在同一个终端里先执行了 sdk use,然后又在别的终端窗口执行 java -version。sdk use 只对当前 shell 会话生效,其他窗口当然是旧版本。这是机制限制,不是 bug。
场景二:你用了 sdk default,但新开的终端还是旧版本。这种情况一般是因为 shell 配置文件里有其他逻辑在启动时覆盖了 JAVA_HOME。常见于 .bashrc 或 .zshrc 里存在手动写的 export JAVA_HOME=xxx,而且这行代码位于 SDKMAN 初始化代码之后,导致它把 SDKMAN 设置的环境变量又覆盖回去了。
排查方法:
bash复制which java
echo $JAVA_HOME
如果 JAVA_HOME 指向了一个 SDKMAN 之外的路径,那就去 .bashrc 或 .zshrc 里找那行手动配置的 export JAVA_HOME,把它注释掉或删除。这其实是很多人从“手动配置环境变量”迁移到 SDKMAN 时最容易踩的坑,SDKMAN 本身没问题,是以前的老配置在作祟。
5.4 卸载 JDK 后环境变量残留的风险
回到前面提到过的热搜词:“java软件删除的时候配置的环境需要删吗”。如果你的 Java 环境不是 SDKMAN 管理的,那么卸载后确实需要手动删除环境变量,更准确地说,要删的是 JAVA_HOME 和 PATH 里指向旧 JDK 路径的条目。如果不删,终端执行 java 时会提示找不到命令,因为路径已经不存在了。有些 IDE 还会因为找不到 JDK 而弹出一堆错误。
通过 SDKMAN 安装的 JDK 卸载后就不存在这个问题。SDKMAN 的动态环境变量机制会自动更新,当你卸载了唯一一个 Java 版本后,它会提示你环境中没有可用的 Java 发行版,但不会影响你的 shell 正常工作,也不会在注册表或系统配置里留下指向无效路径的环境变量。这一点对于 Windows 用户来说尤其省心,因为 Windows 的环境变量修改需要重启终端甚至重启系统才能完全生效,而 SDKMAN 完全绕开了这个限制。
5.5 SDKMAN 自身卸载与升级
如何卸载 SDKMAN?官方提供了一条命令:
bash复制sdk selfupdate uninstall
执行后会删除整个 ~/.sdkman 目录,并尝试清理 shell 配置中添加的初始化代码。不过需要注意的是,它不会主动删除你通过 SDKMAN 安装的 JDK、Maven 等工具,因为那些也都放在 ~/.sdkman/candidates/ 目录下,所以如果你想彻底清干净,得手动删掉整个目录。
升级 SDKMAN 本身:
bash复制sdk selfupdate
更新后可以在 sdk version 里确认版本号。SDKMAN 偶尔会提示你“有新版本可用”,这属于正常现象,更新它对已安装的 JDK 版本没有影响。
6. 工作流优化与效率心得
6.1 用 SDKMAN 统一 CI/CD 构建环境的版本
SDKMAN 日常开发用得多,它在 CI/CD 里其实也很有价值。比如你在 GitHub Actions 或者 Jenkins 的流水线里,可以用 SDKMAN 来安装构建所需的 JDK 和 Maven/Gradle 版本,避免不同构建机上的环境差异。
这里有一个关键技巧:CI 构建环境通常不需要给每个构建都安装一遍全套工具(太慢),你可以把 SDKMAN 装在构建镜像的基础层,然后针对不同的项目在 Executor 里动态切换版本。这样既保持了构建环境的干净,又利用了 SDKMAN 的版本管理能力。
bash复制source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 17.0.10-tem
sdk default java 17.0.10-tem
把这几行加到 CI 脚本里,整个构建流程就能复现出和本地开发一致的环境,减少“本地能过、CI 上失败”的经典问题。
6.2 配合 IDE 使用要注意什么
IntelliJ IDEA 这类 IDE 在检测 Java 环境时,会读取 JAVA_HOME 或者扫描系统里的 Java 安装目录。因为 SDKMAN 默认把 JDK 安装到 ~/.sdkman/candidates/java/ 下,IDE 是能自动识别到的,通常不需要额外配置。
但有一个小坑:如果你在 IDE 里配置了一个“Project SDK”,并指向了某个具体的 JDK 版本,那即使你在终端里切换了 SDKMAN 的 default 版本,IDE 仍然会用它记住的那个版本。这其实是符合预期的,因为 IDE 的项目配置是独立于 shell 环境的。如果你希望 IDE 跟随系统默认版本,可以在 IDE 的设置里把 Project SDK 改成“默认”,或者手动选择 SDKMAN candidates 目录下的对应版本。
另外一个经验:在启动 IDE 之前,先在终端里设置好默认 Java 版本,然后再从终端启动 IDE(比如输入 idea .),这样可以确保 IDE 拿到的环境变量是你预期的。如果你是在桌面图标直接启动 IDE,它继承的是图形界面的环境变量,可能和你终端里的不一样。
6.3 保留一份“纯净版” JDK 作为应急备份
虽然 SDKMAN 很强大,但有时候也会遇到极端情况,比如 ~/.sdkman 目录损坏了,或者系统升级后 SDKMAN 初始化脚本出了问题。这时候如果你手头有一个不依赖 SDKMAN 的 JDK(比如系统自带的 OpenJDK,或者手动安装的 Oracle JDK),就能救急。
我的做法是在 /opt/jdk(Linux)或 ~/tools/jdk(macOS)下保留一个版本稳定、不在 SDKMAN 管理范围内的 JDK,平时基本不用它,但一旦 SDKMAN 出问题,我还能用这个手动安装的 JDK 运行关键任务。这算是一种“双保险”策略,推荐给有系统洁癖的朋友。
当然,这个手动安装的 JDK 就不要加入 PATH 了,避免和 SDKMAN 管理的版本冲突。你需要它的时候,直接在终端里用绝对路径调用就行:
bash复制/opt/jdk/bin/java -version
6.4 SDKMAN 与容器化环境的取舍
如果你的开发环境已经全面容器化,写 Dockerfile 的时候用 FROM eclipse-temurin:17-jdk 这种镜像其实比 SDKMAN 更简单直接,因为容器本身就是隔离的、一次性的,根本不需要“多版本切换”。SDKMAN 的价值在物理机、开发机、长期运行的服务器上更明显。
但这不意味着 SDKMAN 和容器水火不容。有些团队在本地开发流程里会这样用:宿主机上用 SDKMAN 管理 JDK 版本和构建工具,Docker 镜像只用来跑最终的服务运行时。这样写代码、编译、单元测试都跑在宿主机上,反馈快;镜像构建和部署在容器里,环境一致。我个人测试过的很多项目,这种混合模式是最顺畅的。
7. 写在最后的实际操作体会
如果你问我 SDKMAN 有没有缺点,我觉得也有。它的初始化脚本会在每次打开终端时执行,偶感会多消耗那么不到一秒的时间;它对 Windows 原生环境的支持不算好,有些功能要在 WSL 里才能体验到;有时下载访问云不稳定,需要配代理或用离线包。但相对于它带来的便利,这些小问题完全值得包容。
我第一次用 SDKMAN 时的感受是:原来切换 Java 版本可以这么干净利落。以前我为了折腾 JDK 版本切换,甚至写过一堆 shell 脚本来改软链接、改环境变量,后来发现这些脚本在 SDKMAN 面前根本是多余的。现在我接触一台新的开发机或服务器,首先做的就是装 SDKMAN,然后一条命令装好 JDK,再默认装好 Maven 和 Gradle,整个过程不超过五分钟。这种“把工具链的复杂度收敛到一个工具里”的思路,其实才是 SDKMAN 最值钱的地方。
你在用的过程中如果遇到什么奇怪的问题,不妨先去 ~/.sdkman 的日志目录看看。SDKMAN 会给每个安装操作记录日志,排查问题的时候看日志往往比猜原因高效得多。日志目录默认在 ~/.sdkman/var/log/ 下,命名格式大概是 install_java_xxx.log,用 tail -f 慢慢看就行了。多数情况下,问题的原因并不复杂,无非就是网络、权限、路径、配置覆盖这几类。定位到根源,解决起来就很快了。
