Mac 上装 JDK8 这件事,乍看是最没技术含量的活儿,但我这几年帮同事收拾过的烂摊子,十个里有三四个都出在这一步:环境变量写进了不生效的文件、装完 IDEA 死活认不到、M 系列芯片上糊里糊涂跑了 x86 的包,编译慢到怀疑人生。所以这篇就把MacOS 下载安装 JDK8这条链路从头到尾拆一遍,从选发行版、挑芯片架构、下载解压,到写环境变量、验证、IDE 对接,再到那些只有真正踩过才知道的坑。适合三类人看:一是接手了老项目不得不回到 8 的后端同学,二是维护 Android 老工程的移动端同学,三是刚拿到 Mac、连 zsh 和 bash 的区别都还没搞清的学生党。不需要你之前装过任何 JDK,跟着走就行。
1. 先想清楚:为什么还要专门装一个 JDK8
1.1 还在用 JDK8 的几类人,看看有没有你
JDK8 是 2014 年发布的,到 2025 年已经十一年了,但你去翻一翻招聘信息和企业的技术栈盘点,会发现它活得比谁都稳。原因并不复杂:一是历史包袱,很多公司核心业务的代码库就是 8 的语法加上一堆只能在 8 上跑的老框架,迁移成本远大于收益;二是生态依赖,Spark、Hadoop、Flink 的某些版本、以及一堆国产中间件的客户端包,对 8 的支持是最成熟的;三是工具链锁定,Android 早期工程的 AGP 版本、部分老项目的 Maven 插件、某些需要读取tools.jar的代码生成器,都默认你在用 8。
我自己的情况更典型:手上有一个 Spark 2.x 的数据处理项目和一个 2017 年的 Android 工程,这两个东西升 JDK 的收益几乎为零,但风险极高。所以我的 Mac 上常年同时躺着 JDK8、JDK11 和 JDK17 三个版本,靠环境变量和 jenv 切换。你要是也处于这个状态,那这篇内容就是给你写的。
这里顺便说一句,很多人装 JDK8 是因为听说"JDK8 新特性",想学 Lambda、Stream、方法引用、Optional、新的日期时间 API。这个思路没问题,这些特性确实把 Java 的写法从"啰嗦"拉到了"能看",尤其是 Stream 的链式操作和LocalDateTime替换SimpleDateFormat这两件事,写过一次就不想回去了。但学特性和装环境是两码事,环境装不对,代码跑不起来,学什么都白搭。
1.2 装之前必须确认的两件事:芯片架构和"你到底要什么"
第一件事:你的 Mac 是哪种芯片。翻开左上角苹果标,关于本机,如果是 Apple 芯片(M1/M2/M3/M4 系列),那就是arm64;如果是 Intel 处理器,那就是x86_64。这个信息决定了你要下载哪个包,下错了轻则跑不起来,重则能跑但性能打骨折。
第二件事:你要的是"一个能跑的 java 命令",还是"一个能被系统识别、被 IDE 识别的完整 JDK 环境"。这两者的区别在于要不要放进/Library/Java/JavaVirtualMachines或者~/Library/Java/JavaVirtualMachines,也就是 macOS 认的那两个"官方安装位"。很多人随手解压到~/Downloads就直接配 PATH,命令行能用,但 IDEA 的 SDK 列表里空空如也,还得手动 Add SDK 指过去,麻烦。
我的建议是:不管用哪种安装方式,最终都让 JDK 落在系统认可的目录里。这样/usr/libexec/java_home能扫到,IDEA、Eclipse、Maven、Gradle、Tomcat 的启动脚本全都能自动认。省下的时间够你多摸半小时鱼。
2. 选哪个 JDK8:四个主流发行版横向对比
2.1 一张表看懂 Zulu、Temurin、Corretto、Liberica
JDK8 时代过去十年了,Oracle 自己的 JDK8 已经不太适合直接拿来用,现在主流的选择是几个 OpenJDK 的发行版。我把常用的四个拉出来对比一下,这些都是我这几年实际用过的:
| 发行版 | 维护方 | macOS arm64 支持 | 许可证 | 适合谁 |
|---|---|---|---|---|
| Azul Zulu 8 | Azul | 有,且支持较早 | 免费可用于生产 | 最稳妥的默认选择,尤其 M 系列芯片 |
| Eclipse Temurin 8 | Adoptium 社区 | 有 | 免费 | 想要社区背书、CI 环境常用 |
| Amazon Corretto 8 | Amazon | 有 | 免费 | 已经在用 AWS 或者图省心的 |
| Liberica JDK 8 | BellSoft | 有 | 免费 | 需要 JavaFX 打包的场景 |
说几个我自己的取舍逻辑。M1 刚出来的那两年,Zulu 是最早提供原生 arm64 JDK8 的发行版,我那会儿别无选择,就一直用下来了,现在也懒得换。Temurin 是这几个里社区活跃度最高的,CI 流水线里用得最多,如果你要把本地环境跟构建机对齐,选它比较省事。Corretto 是 Amazon 维护的,常年免费,且更新节奏稳定。Liberica 的价值在于它自带 JavaFX,如果你要跑一些桌面小工具,能少折腾一层。
注意:不要混用。在同一台机器上装两三个不同的发行版没问题,但同一个项目里的
JAVA_HOME和 IDE 的 SDK 必须指向同一个。我见过一次诡异的问题——命令行编译通过、IDE 里跑报错,最后发现是两边指向了不同的 patch 版本。
2.2 关于 Oracle 官方 JDK8 的那些坑
有人会问,为什么不用 Oracle 官网下载的 JDK8?两个原因。
第一是版本停更的问题。Oracle 官方提供给 macOS 的 JDK8 安装包,版本号停留在很早的更新号上,后面的安全补丁和时区数据更新基本跟 macOS 用户无关了。而 JDK 的更新里很大一部分是时区数据库、根证书、TLS 相关的修补,落后几个版本在某些网络环境下会直接连不上服务。
第二是许可问题。Oracle 从某个版本之后调整了 JDK8 的授权策略,商业环境下使用需要额外授权,这不是技术问题,但会给公司带来合规麻烦。团队里如果有人图省事直接从官网下,事后被安全部门问起来很难解释。
所以我的结论很明确:macOS 上用 JDK8,优先选 OpenJDK 发行版,别从 Oracle 官网下。这不是技术优劣问题,是省心问题。
2.3 下载文件的三种形态,选错了会多绕两圈
同一个 JDK8,官网通常提供三种下载形态,很多人在这里就开始迷糊了:
| 形态 | 典型后缀 | 安装位置 | 是否需要 sudo | 适合场景 |
|---|---|---|---|---|
| 安装包 | .dmg / .pkg | /Library/Java/JavaVirtualMachines | 需要 | 只想装一次,不想碰命令行 |
| 压缩包 | .tar.gz / .zip | 手动放到任意位置 | 不需要 | 想装在用户目录、不想用管理员密码 |
| 包管理器 | brew cask | /Library/Java/JavaVirtualMachines | 需要 | 习惯用 brew 统一管理 |
| SDKMAN | 脚本托管 | ~/.sdkman/candidates/java | 不需要 | 需要频繁切版本 |
这里面有个细节值得说:.dmg里通常是一个.pkg,双击一路下一步,JDK 会被装到/Library/Java/JavaVirtualMachines,这是系统级目录,需要管理员密码。装完之后系统自带的/usr/bin/java那个 stub 就能找到它,很多时候连 PATH 都不用配,直接java -version就有输出。
而.tar.gz解压出来的是一个目录,你需要自己决定放哪。放~/Library/Java/JavaVirtualMachines(用户级)和/Library/Java/JavaVirtualMachines(系统级)都可以,前者不需要 sudo,后者要。我一般给单个用户用的机器都放用户级,公司的共享 Mac 才放系统级。
3. 三种安装方式,按你的习惯挑一条走
3.1 方式一:Homebrew Cask,一条命令搞定
如果你的 Mac 上已经有 Homebrew,这是最省事的路子。先确认 brew 可用:
brew --version然后直接装 Temurin 8:
brew install --cask temurin@8这个命令会下载 pkg 并调用系统安装器,中途会要你输入开机密码。装完的位置是/Library/Java/JavaVirtualMachines/temurin-8.jdk。
这里有个很多人不知道的副作用:cask 装的 JDK 是被当作"应用"来管理的,所以你不能用brew uninstall temurin@8之外的方式卸载(其实也可以直接删目录,但 brew 的记录会残留)。而且 cask 装的 JDK 在brew outdated里会跟着更新,如果你正在维护一个对 patch 版本敏感的老项目,某天自动升了个小版本导致行为变化,排查起来会很头疼。所以我一般建议:主力开发机上的 JDK8 用 cask 装图省事可以,但一定要把 brew 的自动更新关掉,别让它背着你升级。
Apple 芯片的机器上,cask 会按当前架构选对应的包,不用你操心 arm64 还是 x86_64。这点比自己下 tar.gz 省心。
3.2 方式二:官方 tar.gz 手动解压,最干净最可控
这是我个人最推荐的方式,尤其是你要在一台机器上放多个 JDK 版本的时候。以 Zulu 8 的 macOS arm64 包为例,从 Azul 官网下载页选 macOS、ARM 64-bit、JDK 8,拿到一个类似zulu8.xx.x.xx-ca-macos-aarch64.tar.gz的文件。
# 1. 建好目标目录(用户级,不需要管理员权限) mkdir -p ~/Library/Java/JavaVirtualMachines # 2. 解压到临时目录看一眼结构 tar -xzf ~/Downloads/zulu8.xx.x.xx-ca-macos-aarch64.tar.gz -C /tmp # 3. 解压出来通常是 zulu-8.jdk 这个目录,直接搬进去 mv /tmp/zulu-8.jdk ~/Library/Java/JavaVirtualMachines/ # 4. 确认结构对不对,Home 目录里应该有 bin、lib、jre 这些 ls ~/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home关键就在第 4 步。macOS 上的 JDK 是一个 bundle 结构,真正的 JDK 根目录是xxx.jdk/Contents/Home,JAVA_HOME要指向这里,不是指向.jdk,也不是指向Contents。这个层级搞错,是最常见的"装了但用不了"的原因。
Temurin 的 tar.gz 解压出来名字可能是jdk8u4xx-bxx这种,没有.jdk后缀。虽然/usr/libexec/java_home一般也能识别,但我习惯重命名一下,统一成好认的名字:
mv /tmp/jdk8u412-b08 ~/Library/Java/JavaVirtualMachines/temurin-8.jdk手动方式的另一个好处是卸载特别简单——直接rm -rf那个目录,干干净净,不留任何系统痕迹。cask 和 pkg 装的东西虽然也主要在同一个目录,但总会有些注册、链接之类的残留需要留意。
3.3 方式三:SDKMAN 多版本管理,折腾党首选
如果你同时在维护 JDK8、11、17、21 的项目,SDKMAN 值得装一个。它把所有 JDK 装在~/.sdkman/candidates/java下面,切版本一条命令,不用改环境变量。
# 安装 SDKMAN curl -s "https://get.sdkman.io" | bash source "$HOME/.sdkman/bin/sdkman-init.sh" # 看看有哪些 8 的版本可选,标识符会随更新时间变化 sdk list java | grep -i "8\.0" # 安装(把 8.0.xxx-zulu 换成你实际看到的标识) sdk install java 8.0.xxx-zulu # 临时切到 8 sdk use java 8.0.xxx-zulusdk use只对当前终端窗口生效,关掉就恢复默认,这点比改全局环境变量安全得多。我用它来跑那些"一年只碰两次"的老项目——平时默认 JDK17,需要的时候开个新窗口sdk use java 8.x,跑完就关。
缺点是 SDKMAN 装的 JDK 不在系统认可的目录里,IDEA 有时候扫不到,需要手动 Add SDK 指到~/.sdkman/candidates/java/8.0.xxx-zulu。如果你主要用命令行和 Maven,影响不大。
4. 环境变量:JAVA_HOME 到底该指向哪里
4.1 macOS 独有的 java_home 机制
Linux 上你只能自己写死路径,macOS 多给了一个工具:/usr/libexec/java_home。它会扫描系统里所有已注册的 JDK,并支持按版本号查询:
# 列出所有已安装的 JDK,包括版本和架构 /usr/libexec/java_home -V # 拿到 1.8 的路径,注意版本号写法 /usr/libexec/java_home -v 1.8 # 也可以写 8 /usr/libexec/java_home -v 8它的价值在于:你不用把路径写死。以后换了 JDK8 的 patch 版本,只要还在 1.8 这个系列里,环境变量自动跟着变,不用改配置文件。这在需要频繁更新安全补丁的环境里特别有用。
一个小坑:-v 1.8和-v 8都能用,但-v 1.8.0_412这种精确到补丁号的写法,在有些版本上匹配不到。所以配环境变量的时候用1.8就好,别太精确。
还有,如果java_home -v 1.8没有任何输出(返回空字符串),说明系统根本没扫到你的 JDK8。这时候先排查两件事:目录放对没有(必须在两个JavaVirtualMachines目录之一),以及目录结构对不对(xxx.jdk/Contents/Home这一层必须存在)。
4.2 zsh 下配置文件到底该写哪一个
macOS 从 Catalina 开始默认 shell 换成了 zsh,但网上大量教程还在教人写~/.bash_profile,照着做当然不生效。zsh 的配置文件有好几个,职责不一样,这是最容易搞错的地方:
| 文件 | 加载时机 | 适合放什么 |
|---|---|---|
| ~/.zshrc | 每个交互式 shell 启动时 | 环境变量、alias、PATH,日常首选 |
| ~/.zprofile | 登录 shell 启动时 | 一次性的初始化,登录时执行一次 |
| ~/.zshenv | 所有 zsh 启动时 | 极少数需要全局生效的变量 |
| ~/.zlogin | 登录后 | 很少用 |
实操建议:环境变量写在~/.zshrc里。原因很简单,IDEA、VS Code 的内置终端、各种脚本调起来的子 shell,不一定都是"登录 shell",写.zprofile有可能读不到。写.zshrc覆盖的场景最广。
顺带说一个真实案例。有个同事装完 JDK 之后java -version一直是老版本,查了半天,发现他的 PATH 里/usr/local/bin排在前面,而那里有个 brew 装的 openjdk 的软链接,把新装的 JDK8 给盖住了。所以配完之后一定要用which -a java看一眼,它会列出 PATH 里所有叫 java 的可执行文件,顺序就是优先级。
4.3 一套可以直接抄的配置,加上多版本切换
打开~/.zshrc,加下面这段。我用的是"动态查询 + 兜底判断"的写法:
# JDK8 配置 export JAVA_HOME=$(/usr/libexec/java_home -v 1.8 2>/dev/null) if [ -n "$JAVA_HOME" ]; then export PATH="$JAVA_HOME/bin:$PATH" fi为什么要加2>/dev/null和if判断?因为java_home找不到 JDK 时会把错误信息打到 stderr,而且返回空值。如果不判断,PATH 前面会拼出一个空路径,某些极端情况下会让命令解析出问题,而且每次开终端都会看到一行报错,很烦。
如果你更喜欢写死路径,这样也行,胜在启动快一点(java_home每次执行大概几十毫秒):
export JAVA_HOME="$HOME/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home" export PATH="$JAVA_HOME/bin:$PATH"多版本切换我用函数而不是 alias,因为 alias 每切一次 PATH 就多累积一段,开开关关几十次之后 PATH 会长得没法看:
jdk() { local v="${1:-1.8}" local home home=$(/usr/libexec/java_home -v "$v" 2>/dev/null) if [ -z "$home" ]; then echo "没有找到 JDK $v,用 java_home -V 看看装了哪些" return 1 fi export JAVA_HOME="$home" export PATH="$JAVA_HOME/bin:${PATH//$JAVA_HOME\/bin:/}" java -version }用的时候jdk 1.8、jdk 17这样切,函数会先把旧的 java bin 从 PATH 里剔掉再插新的,不会越积越长。比 jenv 轻量,不用额外装东西。
改完配置记得让当前窗口生效:
source ~/.zshrc注意:
source只对当前窗口有效,已经开着的其他终端窗口不会自动刷新。验证的时候一定要新开一个终端,不然你看到的还是旧环境,容易得出错误结论。
5. 装完怎么验证:三个命令加 IDE 对接
5.1 三行命令自检
装完之后跑这三条,基本能确定环境是好的:
java -version javac -version echo $JAVA_HOME预期输出是这样的(以 Zulu 8 为例):
openjdk version "1.8.0_412" OpenJDK Runtime Environment (Zulu 8.76.0.17-CA-macos-aarch64) OpenJDK 64-Bit Server VM (Zulu 8.76.0.17-CA-macos-aarch64) mixed mode javac 1.8.0_412 /Users/yourname/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home几个判断要点。第一,java -version和javac -version的版本号必须一致,如果不一致,说明 PATH 里有多个 JDK,java和javac分别来自不同地方,这是个典型的环境污染,编译出来的 class 版本可能对不上。第二,括号里那句会显示发行版和架构,如果看到aarch64说明是原生 arm64 版本,看到x86_64说明走的是 Rosetta,在 M 系列芯片上性能会有损耗。第三,echo $JAVA_HOME必须以Contents/Home结尾。
再加一条更全面的排查命令:
which -a java /usr/libexec/java_home -V前者看 PATH 里有哪些 java,后者看系统里注册了哪些 JDK。
5.2 IDEA、Maven、Gradle 里怎么指到 JDK8
命令行通了不代表 IDE 通了。IDEA 里要改两个地方,这两个地方是独立的,很多人只改一个然后困惑为什么还是没用。
第一个地方是项目 SDK:File、Project Structure、Project、SDK 选 1.8,Language level 也选 8。如果下拉框里没有 1.8,点 Add SDK、JDK,然后选到Contents/Home那一层。
第二个地方是构建工具的 JDK。Gradle 项目要单独看 Settings、Build Tools、Gradle 里的 Gradle JVM;Maven 项目看 Runner 里的 JRE 配置。这两个设置跟项目 SDK 是分开的,很容易漏。
Maven 的话,命令行下直接受JAVA_HOME影响,所以source ~/.zshrc之后就会用 8。但有个坑:IDEA 里的 Maven 默认用的是 IDE 内置的 JRE,不是你的JAVA_HOME。要在 Settings、Build Tools、Maven、Runner 里显式指定 JRE 为 1.8,否则会出现"命令行能编译,IDEA 里编译报错"的诡异现象。
Gradle 的坑更明显一些。JDK8 能跑的 Gradle 版本是有上限的,新版 Gradle 和 Android Gradle Plugin 会要求 JDK11 甚至 17。老项目通常锁定在 Gradle 6.x 或 7.x 配合 JDK8,如果你不小心用新版本 Gradle 去跑,会直接报"不支持的类文件版本"之类的错误。遇到这种情况别急着换 JDK,先看gradle/wrapper/gradle-wrapper.properties里的版本号对不对。
5.3 关于 JAVA_HOME 指向 JRE 的坑
JDK8 的目录结构里有个jre子目录,因为 8 时代 JDK 和 JRE 是分开打包的。有些教程会让人把JAVA_HOME指向Contents/Home/jre,这是错的。
区别在哪里?jre目录里只有运行时的东西,没有javac,也没有lib/tools.jar。而 JDK8 时代相当多的构建工具——比如某些老版本的 Maven 插件、Groovy 相关的代码生成器、Lombok 的早期实现——需要读tools.jar才能工作。JAVA_HOME指错到 jre,症状就是编译期各种NoClassDefFoundError或者tools.jar not found,报错信息跟 JDK 版本八竿子打不着,排查起来很痛苦。
正确的判断方式很简单:
ls $JAVA_HOME/bin/javac ls $JAVA_HOME/lib/tools.jar两个文件都存在,说明JAVA_HOME指对了。缺任何一个,回头检查路径。
6. 踩坑实录:Mac 装 JDK8 最容易翻车的六个地方
6.1 "已损坏,无法打开"和 Gatekeeper
从浏览器下载的 dmg 或 tar.gz,macOS 会给它打上一个com.apple.quarantine扩展属性。解压出来的文件可能继承这个属性,双击运行的时候就会弹窗说"已损坏,无法打开,你应该将它移到废纸篓",或者"无法验证开发者"。
这个提示不是文件真的坏了,是系统安全机制拦的。有两种处理方式。第一种是从系统设置里点"仍要打开"——系统设置、隐私与安全性、找到那条拦截记录、点"仍要打开"。图形界面操作,适合只装一次的人。
第二种是用命令行批量清掉这个属性,适合自动化脚本:
sudo xattr -rd com.apple.quarantine ~/Library/Java/JavaVirtualMachines/zulu-8.jdk如果装在系统目录就把路径换成/Library/Java/JavaVirtualMachines/...。-r是递归,-d是删除指定属性,-c是清空所有扩展属性。我一般用-rd,比较精准,不会误删别的属性。
提示:如果
xattr -c之后还是提示损坏,多半是下载不完整,文件真的损坏了。对比一下官网给的 SHA256 校验值,这个步骤能省下很多无谓的排查时间。
6.2 命令找不到,或者新终端里不生效
"java: command not found"是最高频的问题,原因一般有四种,按概率排序:
第一种,配置文件写错文件了。写进了.bash_profile而当前用的是 zsh,或者写进了.zprofile但当前窗口不是登录 shell。判断方法:echo $SHELL看当前用的是哪个 shell,echo $JAVA_HOME看变量有没有加载上。
第二种,写对了但没source,或者当前窗口是改之前就开着的。新开一个窗口试试。
第三种,PATH 顺序被别的 JDK 盖住了。which -a java能看到全部候选,排第一的就是实际生效的那个。解决办法是把你想要的 JDK 路径往 PATH 前面塞。
第四种,JAVA_HOME拼错了目录层级,比如指到了xxx.jdk而不是xxx.jdk/Contents/Home,或者反过来多写了一层。这会导致$JAVA_HOME/bin这个目录根本不存在,PATH 里加了个无效路径,自然找不到 java。
还有一种比较隐蔽的情况:装了 pkg 之后,/usr/bin/java那个 stub 应该能工作,但如果系统里注册的 JDK 一个都没有,它会提示"没有 Java 运行时,是否要安装"。这时候跑一下/usr/libexec/java_home -V,如果输出是空或者只有一行 "Unable to find any JVMs matching version",说明系统压根没扫到你的 JDK,回去检查目录位置和结构。
6.3 常见问题速查表
把上面这些和其他一些零碎的整理成表,出问题的时候直接对号入座:
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
| java: command not found | PATH 没配或配置文件写错 | 检查 .zshrc,source 后新开窗口验证 |
| java -version 版本不对 | PATH 里有多个 JDK | which -a java 查看顺序并调整 |
| java 和 javac 版本不一致 | 两个可执行文件来自不同 JDK | 统一 JAVA_HOME 与 PATH 来源 |
| 提示已损坏或无法验证开发者 | quarantine 属性 | xattr -rd com.apple.quarantine |
| IDEA 里找不到 1.8 SDK | 路径没指到 Contents/Home | 手动 Add SDK 指到正确层级 |
| 编译报 tools.jar not found | JAVA_HOME 指到了 jre 目录 | 改为指向 Contents/Home |
| M 系列芯片上性能很差 | 装的是 x86_64 包走 Rosetta | 换原生 arm64 包重新安装 |
| 报 UnsatisfiedLinkError | 依赖库只有 x86_64 版本 | 装 x86_64 版本并配合 Rosetta |
| java_home -v 1.8 无输出 | JDK 没放进认可目录 | 移到 JavaVirtualMachines 下 |
这张表里有两行值得展开说。
关于 M 系列芯片上装 x86_64 的 JDK8,这不是错误做法,有时候是唯一做法。原因是一些老项目的 native 依赖,比如某些数据库驱动、压缩库、图形库的 dylib,只有 x86_64 版本。你在原生 arm64 的 JDK 上跑,一加载这些 native 库就抛UnsatisfiedLinkError。这种情况下装 x86_64 的 JDK8,让整个进程在 Rosetta 下运行,反而能跑通。代价是性能有损耗,实测编译时间大概多个百分之二三十,日常开发能忍。
要装 Rosetta 的话,先执行:
softwareupdate --install-rosetta --agree-to-license然后下载 x86_64 版本的 JDK 包,装完用java -version确认括号里显示的是x86_64。想强制用某个架构运行,可以加arch前缀:
arch -x86_64 /path/to/java -version关于"javac 版本不一致",这个坑很隐蔽。有些人 PATH 里同时有 brew 装的 openjdk(提供 java)和手动装的 JDK8(提供 javac),或者反过来。这时候java -version显示 8,javac -version显示 17,写代码用新语法,编译出来的 class 版本又对不上运行环境,报错信息会非常绕。养成习惯:每次配完环境,两个命令都跑一遍对一下。
7. 换机和重装之后,怎么在十分钟内把 JDK8 环境恢复回来
这一节是给经常重装系统、或者换了新 Mac 的人准备的。我自己过去两年重装过三次系统,换过一次机器,前两次都花了半天时间在重新配环境上,第三次我学乖了,做了套备份方案,实测十分钟内能恢复。
核心思路是:JDK 本体、环境变量片段、项目里的工具链配置,这三样东西要能一键还原。
第一,JDK 本体不要每次都重新下载。Zulu 的 tar.gz 大概是 200 多 MB,公司网速慢的时候能下一小时。我习惯把它和几个常用版本的 JDK 一起放在移动硬盘的env-backup目录里,重装之后直接解压到~/Library/Java/JavaVirtualMachines,一步结束。注意不要直接备份已安装好的目录然后拷回去——权限信息可能丢失,复制完之后最好跑一次:
chmod +x ~/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/*把可执行权限补回来,不然会报"权限不够"或者"无法执行二进制文件"。
第二,环境变量片段独立成文件。我不把 JDK 配置直接写在.zshrc里,而是单独放一个~/.zshrc.d/java.zsh,然后.zshrc里加一行循环加载:
for f in ~/.zshrc.d/*.zsh; do [ -r "$f" ] && source "$f" done这样做的好处是:备份的时候只需要把这个目录扔进这个 dotfiles 仓库,恢复的时候克隆下来,source ~/.zshrc就全回来了。而且以后加新的环境配置(Python、Node、Go)也是同样的方式,互不干扰。
第三,记录一份环境快照。重装前先跑一遍这几条命令,把输出存成文本放到云笔记里:
/usr/libexec/java_home -V > ~/env-snapshot.txt which -a java >> ~/env-snapshot.txt java -version 2>&1 >> ~/env-snapshot.txt echo $JAVA_HOME >> ~/env-snapshot.txt内容的长度不超过一屏,但恢复的时候能帮你快速对齐版本号和路径。我有一次就是因为没记快照,重装后随手装了个新一点的 patch 版本,结果一个老项目的某个序列化行为变了,排查了整整一晚上才发现是 JDK 小版本差异。从那以后我每次都记。
第四,把常见的坑先写进文档里,不要靠记忆。我在自己的 dotfiles 仓库根目录放了一个TROUBLESHOOT.md,里面就是本文 6.3 那张表。理由是重装后往往还在时差或者疲惫状态,判断力下降,照着表走比临时搜索靠谱得多。
最后说一个我自己的实际使用体验。现在我的 Mac 上装的是 Zulu 8 和 Temurin 17 两个版本,日常默认 17,遇到老项目就在终端里jdk 1.8切过去,IDEA 里那两三个老工程的 SDK 单独配好不动。这套组合用了两年多,没再出过环境问题。唯一需要留意的是每次 macOS 大版本升级后,系统权限模型偶尔会调整,某个 JDK 目录的访问权限可能需要重新确认一次,装完之后立刻跑一遍java -version和javac -version就能及时发现,别等到项目打开才发现跑不起来。