ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MacOS 安装 JDK8 完整指南:发行版选择、环境变量与多版本共存

MacOS 安装 JDK8 完整指南:发行版选择、环境变量与多版本共存 1. 为什么现在的 Mac 上还留着 JDK8 的位置聊 MacOS 下载安装 JDK8 这件事本身就带着一点“逆时代”的味道。毕竟 JDK 已经走到 20 多个版本LTS 都换了好几轮随便一个新项目脚手架拉下来默认都是 17 或者 21。但只要你接过维护类的工作或者手上有一批跑了五六年的老系统就会发现 JDK8 像客厅里那张搬不走的旧沙发——占地方可你还真得坐。我手上现在维护的三个服务里有两个还是1.8.0_3xx的编译目标本地要是没有 JDK8连mvn clean package都过不去。这篇内容写给三类人第一类是刚换 Mac、准备搭 Java 开发环境的新人看到网上教程五花八门不知道该信哪个第二类是接手了老项目的开发者本地 JDK 版本跟项目对不上编译报错一堆第三类是需要在同一台机器上同时维护新旧项目的人想让 JDK8 和 JDK17 和平共处。这三种场景的安装路径其实很不一样尤其是最后一类随便装一个很容易把默认版本搞乱后面 IDE、Maven、Gradle 全跟着出问题。我把整件事拆成了几个环节先选对发行版和芯片架构再选安装方式然后是环境变量配置接着是多版本共存最后是踩坑排查。每一环都有几个容易被忽略的细节我会把“为什么这么做”也一并说清楚这样你换个环境、换个 JDK 版本照样能自己推导出正确做法而不是死记某一条命令。1.1 老项目的现实约束JDK8 在语法层面并不弱。Lambda 表达式、Stream 流式处理、方法引用、接口默认方法、Optional 以及全新的java.time日期时间 API这些在今天依然是主力写法。很多团队迟迟不升级不是因为不知道新版本好而是升级成本太高依赖的中间件客户端对高版本 JDK 的模块化限制不兼容某些反射调用在 JDK9 之后会直接抛InaccessibleObjectException老版本的 Lombok、ASM、CGLIB 在 JDK17 上编译期就崩。我见过最极端的一个项目光是让 Spring Boot 1.x 跑在 JDK11 上前前后后折腾了两周最后结论就是继续用 JDK8。所以“在 Mac 上装 JDK8”这件事本质上不是一个技术选型问题而是一个环境适配问题。你的目标不是追新是让本地环境和生产环境、和团队其他成员的环境保持一致。这一点想清楚后面很多选择就顺理成章了既然是为了跑老项目那就没必要追求最新版本号稳定、好找、路径规范比什么都重要。1.2 三类不同诉求的人分别要注意什么纯新人学习重点是装一个能用的就行别折腾多版本路径记住一次即可。推荐用包管理器一把梭省去手工配环境变量的麻烦。维护老项目的开发者重点是版本号要和线上尽量贴近。1.8.0_202和1.8.0_402在大方向上兼容但某些加密算法、TLS 协议默认值、时区数据库有差异涉及这些逻辑的项目最好对齐。新旧项目并行的人重点是“不动全局默认版本”。你在.zshrc里把JAVA_HOME写死成 JDK8新项目那边立刻就能感受到恶意。这类情况必须上切换工具或者至少在 IDE 和构建工具里做局部指定。这三类人的路径不同但有个共同点都要先搞清楚自己要装的是哪个发行版、哪个架构。这部分内容是整个流程里最容易出错、也最容易被教程糊弄过去的地方。2. 下载之前先做选择发行版、版本号与芯片架构很多人下载 JDK8 的第一反应是去某度搜“JDK8 下载”点进第一个结果下载一个不知道哪个机构打包的安装包。这个习惯得改。JDK 的发行版之间差异是实打实的涉及授权、更新频率、架构支持、证书根签发方装错了轻则keytool报错重则整个构建链路的信任链对不上。另外还要注意一个现实问题MacOS 从 Catalina 开始就不再预装 Java 了/usr/bin/java只是个转发器第一次运行会弹窗让你去装运行时。所以你必须自己装一套完整的 JDK而不是只装 JRE——javac、jar、keytool、jps这些工具在做开发时都会用到。2.1 Oracle JDK 8 从哪个版本开始变了授权这是一个绕不开的历史节点。Oracle JDK 8 从8u211 / 8u212开始切换到新的授权协议商用场景需要付费订阅。8u202 及之前的版本仍可以免费用于商业用途。这就是为什么你在网上会看到大量教程强调“认准 8u202”这个版本号。实际操作里8u202 有几个尴尬的地方一是版本太老发布于 2018 年底之后的很多安全补丁拿不到二是官网把他归到了归档页面下载需要点好几个“接受协议”的勾选框路径藏得比较深三是在 Apple Silicon 的机器上跑 x64 版本的 8u202需要额外装 Rosetta而且实测启动速度确实一般。我的建议是这样如果是公司内部项目、法务那边卡得严那就老老实实用 8u202 或者走 OpenJDK 系的开源发行版如果是个人学习、开源项目、或者公司已经采购了相应的支持那选社区维护的开源发行版更省心补丁更新也更及时。这个判断你自己做我后面会分别给出对应的下载和安装方式。2.2 Temurin、Zulu、Corretto 横向对比开源阵营里几个主流的选择我整理成了下表方便你按自己的场景挑。要注意的是下面这些结论基于我实际使用和社区反馈的综合印象具体某个小版本的行为还是以官方发布页为准。发行版维护方MacOS 支持情况安装方式适合场景TemurinEclipse Adoptium提供 Intel 与 Apple Silicon 包具体见官网标注Homebrew Cask、pkg、tar.gz通用首选社区生态好ZuluAzul Systems对 Apple Silicon 支持较早8 系列有对应包Homebrew Cask、tar.gz、pkgM 系列芯片、需要旧版本CorrettoAmazon提供 macOS 安装包Homebrew Cask、pkg与云上环境保持一致Oracle JDKOracle提供 pkg8u202 之后商用需授权dmg/pkg有订阅或有明确合规要求选哪个其实差别不大字节码层面都是标准 JDK跑起来不会有兼容性问题。真正的差异体现在一是证书库里的根证书集合略有不同涉及 HTTPS 双向认证的项目要留意二是某些版本对字体渲染、AWT/Swing 的处理有细微差别做桌面应用的团队要测一下三是更新节奏不同有的跟随 Oracle 的季度更新有的会滞后几个星期。我个人在 M 系列 Mac 上的习惯是优先看官网页面有没有标aarch64或arm64有就选它没有就退回 x64 加 Rosetta。这不是玄学原生的 arm64 版本在启动 JVM、编译大型项目时确实快一截内存占用也更合理。2.3 Intel 与 Apple Silicon装错架构会怎样这个坑我必须单独拎出来讲。MacOS 的芯片架构已经分成两代uname -m在 Intel 机器上返回x86_64在 M 系列上返回arm64。如果你在 M 系列机器上装了 x86_64 版本的 JDK系统会静默调用 Rosetta 2 来翻译运行表面上“能用”但会带来几个后果一是启动变慢。JVM 冷启动那一下翻译成本很明显我实测过一个 Spring Boot 小项目arm64 版本启动 1.8 秒左右x64 版本要 3 秒多。二是内存占用偏高Rosetta 的地址空间映射和原生不一样长时间跑单元测试或者本地起服务机器发热会明显一些。三是某些依赖本地库的场景会出问题比如 JNI 调用、Netty 的 native transport、Lucene 的向量化指令集架构不匹配时可能直接加载失败。验证方法很简单装完之后跑一下java -XshowSettings:properties -version 21 | grep os.arch输出aarch64说明是原生 arm64输出x86_64就是走 Rosetta 了。目录名上也能看出来/Library/Java/JavaVirtualMachines/下面如果只有temurin-8.jdk这种干净的命名需要进到Contents/Home里用上面那条命令确认。3. 三种安装路径的完整实操装 JDK8 这件事MacOS 上大致有三条路包管理器、官网 pkg、手动解压。三条路都能到达终点区别在于可维护性、权限要求和出问题时的排查难度。我按推荐程度从上到下排。3.1 Homebrew Cask一条命令搞定前提是机器上已经有 Homebrew。没装的先补上安装脚本网上到处都是这里不展开。装好之后确认一下 Homebrew 本身是正常的brew --version brew doctorbrew doctor会给你一堆 Warnings大部分可以忽略只要不是明确报错的就行。接着安装 JDK8brew install --cask temurin8想要其他发行版的话包名对应关系是zulu8、corretto8。安装过程中可能会要求输入密码因为要往/Library/Java/JavaVirtualMachines/里写文件。装完之后验证/usr/libexec/java_home -v 1.8这条命令会打印出 JDK8 的家目录路径类似/Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home。能打印出来就说明系统层面已经识别到了。Homebrew 这条路最大的好处是卸载干净。以后不用了brew uninstall --cask temurin8一条命令收拾利索不会在系统里留一堆配置文件。缺点是版本号不完全由你控制上游什么时候更新你就跟着更新偶尔会遇到某个小版本跟你的老项目不太对付的情况这时候可以用brew pin锁版本或者干脆换手动安装。注意Cask 安装的 JDK 不带 Java 控制面板也不改系统默认的java指向。它只是把文件放到位剩下的环境变量要你自己配。3.2 官网 pkg 图形安装最保险的做法如果你需要指定某个精确的小版本号比如必须用 8u202那就得去官网下载 pkg 手动装。流程不复杂但有几个细节容易翻车。下载页面一般会按操作系统分类选 macOS 之后会看到两种包x64和aarch64。根据自己的芯片选。下载下来是个.dmg双击挂载里面有个.pkg双击运行一路下一步中途会要求输入管理员密码。装完之后同样用/usr/libexec/java_home -V确认一下。这里有个坑如果你之前装过同系列的其他版本pkg 安装可能会覆盖或者并存java_home的管理机制是看目录下的.jdk文件夹多个版本会都列出来具体哪个生效取决于版本号大小和-v参数。命令行安装 pkg 也是可以的适合写自动化脚本的场景sudo installer -pkg /Volumes/JDK8/jdk-8u202-macosx-x64.pkg -target /这种方式的好处是版本完全可控坏处是升级不方便每次都得重新下载、重新装、手动清理旧版本。第一次运行从官网下载的 pkg 时可能会被 Gatekeeper 拦住提示“无法打开因为 Apple 无法检查其是否包含恶意软件”。处理方式是去“系统设置 → 隐私与安全性”在最下面会看到刚才被拦截的条目点“仍要打开”即可。这个提示跟包本身的安全性无关只是签名验证走了另一条链路。3.3 tar.gz 手动解压没有 sudo 权限时的解法公司电脑权限管得严、或者你只是想临时试一下可以用 tar.gz 版本。下载下来解压到一个你自己有权限的目录比如~/dev/jdk/mkdir -p ~/dev/jdk tar -xzf OpenJDK8U-jdk_aarch64_mac_hotspot_8u4xx.tar.gz -C ~/dev/jdk解压出来的目录名可能是jdk8u4xx-b07这种为了方便识别改个名mv ~/dev/jdk/jdk8u4xx-b07 ~/dev/jdk/jdk-8然后用export JAVA_HOME~/dev/jdk/jdk-8就能用了。这种方式的坑在于系统级的/usr/libexec/java_home认不到这个 JDK因为它的扫描范围是固定的几个目录。如果你用的工具链依赖java_home比如一些构建脚本、IDE 的自动探测就会找不到。解决办法有两个一是把解压后的目录通过符号链接放到/Library/Java/JavaVirtualMachines/下需要 sudo二是配好JAVA_HOME之后就不管java_home了所有工具都显式指向。手动安装的另一个好处是干净。整个 JDK 就是一个文件夹不想要了直接rm -rf不会在系统里留下任何东西。我本人在临时验证某个版本行为的时候基本都用这种方式。4. 环境变量怎么配才不会白装JDK 装完只是把文件放到了硬盘上终端里敲java -version能不能用、用哪个完全取决于环境变量。这一步是新手最容易糊弄过去、也是后续问题最多的地方。4.1 zsh 下到底改哪个文件macOS 从 Catalina 开始默认 shell 换成了 zsh对应的配置文件是~/.zshrc。但这里有个细节zsh 会区分登录 shell 和非登录 shell会按顺序读取.zshenv、.zprofile登录时、.zshrc交互式、.zlogin。图形界面启动的终端一般会读.zshrc但从 Finder 或者某些 IDE 里启动的进程环境来源可能完全不同。所以我的做法是把JAVA_HOME写在~/.zshrc里同时也在~/.zprofile里写一份。两份内容一致代价是维护成本略高但能覆盖绝大多数场景。编辑命令open -e ~/.zshrc用文本编辑器打开比用 vim 更直观尤其是要往里粘贴多行配置的时候。改完保存然后source ~/.zshrc让它立即生效。4.2 写死路径与 java_home 动态取值两种写法各有适用场景我分别说。写法一硬编码路径export JAVA_HOME/Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home export PATH$JAVA_HOME/bin:$PATH简单直接一眼就能看出用的是哪个版本。缺点是路径写死某天你换了发行版或者改了目录名就得回来改这一行。写法二动态取值export JAVA_HOME$(/usr/libexec/java_home -v 1.8) export PATH$JAVA_HOME/bin:$PATH好处是路径由系统扫描得出换发行版不用改配置。需要注意的是-v 1.8这个写法不是-v 8也不是-v 1.8.0。历史上 JDK 的版本号在 1.8 和 8 之间有个命名断层java_home认的是1.8。如果机器上有多个 1.8 版本它会返回版本号最高的那个。还有一点$(...)这种写法在每次打开终端时都会执行一次命令会有几十毫秒的开销。可以接受但如果你对终端启动速度特别敏感可以改成硬编码。注意PATH里$JAVA_HOME/bin必须放在前面。如果放后面系统自带的/usr/bin/java那个转发器会先被匹配到导致你敲java用的还是别的版本。4.3 四条命令验证安装结果配置完别急着关终端跑一遍下面这几条全部符合预期才算真的配好。which java # 期望输出/Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java java -version # 期望输出openjdk version 1.8.0_4xx ... echo $JAVA_HOME # 期望输出/Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home javac -version # 期望输出javac 1.8.0_4xx四条命令里java和javac的版本号要一致这是判断有没有装成“混血”的关键。有一种常见情况是之前装过 JDK17PATH里保留了它的路径你又加了个 JDK8 的JAVA_HOME结果java走的是 8javac走的是 17编译出来的 class 文件版本是 61运行时报UnsupportedClassVersionError。这种问题排查起来费时间一开始就检查清楚能省很多事。5. JDK8 与新版 JDK 共存的切换方案一台 Mac 上同时存在 JDK8、JDK17、JDK21这是很常见的状态。麻烦在于“默认用哪个”。我见过有人为了图省事每次切项目就去改一遍.zshrc改完忘了 source然后花半小时找为什么编译不过。这属于给自己制造工作量有必要一次性解决。5.1 手写一个切换函数最轻量的做法在.zshrc里加两个 aliasalias jdk8export JAVA_HOME$(/usr/libexec/java_home -v 1.8) export PATH$JAVA_HOME/bin:$PATH java -version alias jdk17export JAVA_HOME$(/usr/libexec/java_home -v 17) export PATH$JAVA_HOME/bin:$PATH java -version alias jdk21export JAVA_HOME$(/usr/libexec/java_home -v 21) export PATH$JAVA_HOME/bin:$PATH java -version需要哪个版本就敲哪个切换后会自动打印版本号确认。缺点是alias展开后修改的是当前 shell 会话的环境变量开新终端会回到.zshrc里的默认值。这个行为其实是优点能保证每个新终端有一个可预期的起点。如果你想让切换更聪明可以写成一个函数按当前目录自动判断jdk_auto() { if [ -f .java-version ]; then export JAVA_HOME$(/usr/libexec/java_home -v $(cat .java-version)) export PATH$JAVA_HOME/bin:$PATH fi }再配合 zsh 的chpwd钩子cd到项目目录时自动切换。这个方案我在几个多项目并行的仓库里用过体验不错代价是每次cd都有一点点延迟而且.java-version文件得记得维护。5.2 jenv 的用法与代价jenv是一个专门的版本管理工具思路跟nvm类似。安装brew install jenv echo export PATH$HOME/.jenv/bin:$PATH ~/.zshrc echo eval $(jenv init -) ~/.zshrc然后把你机器上所有的 JDK 注册进去jenv add $(/usr/libexec/java_home -v 1.8) jenv add $(/usr/libexec/java_home -v 17)之后就能用jenv global 1.8、jenv local 17来分别设置全局和目录级版本。其中jenv local会在当前目录生成一个.java-version文件效果就是前面手动实现的那套逻辑。jenv的代价有两个一是它在JAVA_HOME外面包了一层 shim某些工具尤其是一些老旧的构建脚本或者 Docker 相关的插件读取JAVA_HOME时可能会拿到 shim 目录而不是真实的 JDK 目录导致识别失败二是它会给每个 shell 会话增加一点初始化开销如果你的.zshrc已经很臃肿感知会比较明显。我的态度是项目多、切换频繁用jenv值得只是偶尔切一次手写 alias 就够了。工具本身没有高下看使用频率。5.3 IDE 与 Maven 各自指定 JDK这一条特别重要而且经常被忽略。IntelliJ IDEA里JDK 的指定分成三个层级别搞混Project Structure → Project → SDK项目级别的默认 SDK影响整个工程的编译和语言级别。Project Structure → Modules → Dependencies → Module SDK模块级别可以覆盖项目级别。Settings → Build Tools → Maven → Runner → JREMaven 运行时的 JRE决定mvn命令用哪个 Java。三层如果设得不一致就会出现“IDE 里编译通过命令行mvn package报错”或者反过来的情况。接手老项目时的标准动作就是先把这三处对齐。命令行 Maven这边mvn脚本会优先读JAVA_HOME读不到才用PATH里的java。所以最省事的做法就是前面配好的 alias切一次版本IDE 里的 Maven Runner 也设成Use JAVA_HOME两边就同步了。Gradle更麻烦一点它有org.gradle.java.home这个属性可以写在gradle.properties里优先级高于环境变量。老项目里经常能看到这一行硬编码换了机器路径对不上就报错。遇到这种情况改掉它或者删掉让 Gradle 走环境变量。6. 装完之后的常见报错与排查以下问题都是我自己或者同事实际遇到过的整理成速查的形式。表格里的“可能原因”按发生概率从高到低排。6.1 安装环节的问题现象可能原因处理方式pkg 双击没反应Gatekeeper 拦截系统设置 → 隐私与安全性 → 仍要打开安装时提示“需要管理员权限”账号不是管理员用有管理员权限的账号或用 tar.gz 方式装到用户目录Homebrew 安装卡在下载网络问题或上游镜像慢brew --cache看缓存目录手动下载后放入或换用官网 pkg装完java_home找不到目录名不以.jdk结尾重命名为xxx.jdk形式提示磁盘空间不足JDK 加 IDE 加缓存占了太多清~/Library/Caches检查 Time Machine 本地快照6.2 环境变量与终端的问题现象可能原因处理方式java -version还是旧版本PATH顺序不对把$JAVA_HOME/bin提到PATH最前面改了.zshrc但不生效没source或写错了文件执行source ~/.zshrc确认当前 shell 是 zsh新终端里变量丢失写在了会话级而不是配置文件写进~/.zshrc和~/.zprofileIDE 里读不到JAVA_HOMEGUI 启动不继承终端环境在 IDE 设置里直接指定 JDK 路径或用launchctl setenvjava和javac版本不一致多版本路径混在PATH里用which -a java javac找出所有来源逐个清理关于 GUI 应用环境变量这一条补充一句从 Finder、Dock、Spotlight 启动的应用环境变量来自系统登录会话不读.zshrc。所以你在终端里配好的JAVA_HOME对图形界面启动的 IDE 是无效的。这是 macOS 上引发“终端能跑、IDE 不能跑”类问题最常见的原因。6.3 运行与编译阶段的问题报错信息含义处理方式UnsupportedClassVersionErrorclass 文件版本高于运行时检查编译和运行的 JDK 是否一致NoClassDefFoundError: javax/xml/bind/...JDK11 之后移除了 Java EE 模块换回 JDK8或在 JDK11 里加依赖java.lang.reflect.InaccessibleObjectException模块化限制加--add-opens参数或换 JDK8Unable to locate a Java Runtime只有转发器没有真 JDK装完整 JDK 并配好环境变量Certificate chain not found证书库差异用keytool手动导入缺失的根证书6.4 卸载与清理不用的 JDK 该清就清尤其是那种装了七八个版本的机器磁盘空间和排查成本都是实打实的。# 查看系统识别到的 JDK /usr/libexec/java_home -V # 删除某个版本路径来自上面命令的输出 sudo rm -rf /Library/Java/JavaVirtualMachines/temurin-8.jdk # Homebrew 安装的用这个 brew uninstall --cask temurin8 # 顺手看一眼残留在系统里的安装收据 pkgutil --pkgs | grep -i jdk用 pkg 安装的版本rm -rf删掉文件夹之后收据信息可能还在某些情况下会导致重新安装时提示“已安装”。这时候可以用sudo pkgutil --forget 包标识清掉记录。Oracle JDK 还会在~/Library/Application Support/Oracle/Java和偏好设置面板里留东西一起删掉更干净。7. 几条实测下来的经验折腾过十几台 Mac 的开发环境有几个体会想单独说说。第一/Library/Java/JavaVirtualMachines/这个目录是 macOS 上 JDK 的“官方停车位”。不管用哪种方式装最后都把 JDK 落到这里能让java_home、IDE 自动探测、各种构建脚本都省心。手动解压的方案虽然干净但代价就是脱离了这个体系后面用工具的时候容易出意外。第二版本号别追求最新。JDK8 的小版本更新里偶尔会有改动默认 TLS 协议版本、调整加密套件优先级这类行为对老系统来说是不小的冲击。如果你的项目线上跑的是1.8.0_202本地装1.8.0_4xx之前先在测试环境验证一轮。我踩过一次本地编译没问题部署到测试环境之后某个 HTTPS 调用直接超时查了半天才发现是本地 JDK 版本太新默认开启的 TLS 版本变了。第三.jdk这个后缀名不是可有可无的。macOS 判断一个目录是不是 JDK就是看它名字结尾是不是.jdk然后再去Contents/Home下找bin/java。手动解压的目录如果叫jdk8u402java_home完全不会认。这个规则没有文档明写但实测有效。第四装环境这件事能写脚本就别手敲。把brew install、环境变量、验证命令整理成一个setup.sh换新机器的时候直接跑一遍五分钟搞定。我现在的脚本里还带了卸载函数反向操作也一并准备好避免哪天真要清理的时候满系统找残留。第五M 系列芯片上跑老版本 JDK如果遇到莫名其妙的崩溃或者 native 库加载失败第一件事就是确认架构。java -XshowSettings:properties -version里的os.arch是最快的判断依据。如果是x86_64而机器是 arm64那就考虑换一个原生版本往往问题就消失了。至于后续怎么扩展这套环境看你的实际需求。有人需要在同一台机器上模拟多种 JDK 组合做兼容性测试那就在~/.zshrc里多写几组 alias有人要给团队做统一的环境配置文档那就把整套流程写进 README附上验证命令让每个人装完自己跑一遍确认。这些都没有标准答案按自己的场景来就行。
返回列表