赞
踩
我的系统版本:macOS Sonoma 版本 14.2.1
默认使用shell:zsh
前不久重置了我的Mac,所以要重新配置Java开发的相关环境,在配置过程中发现了一个神奇的问题。
核心结论是: mac 系统命令行中输入 java 或者 javac 命令,默认调用的都是 /usr/bin/java 和 /usr/bin/javac
这里的 /usr/bin/java 是mac系统帮你创建的一个代理命令,会自动去一些目录下寻找实际安装的 jdk,调用里面的java 命令
去找的目录主要是 /Library/Java/JavaVirtualMachines 这个目录,这也就是为什么网上很多mac系统安装jdk的教程都要有在这个目录下创建一个软连接的步骤。
注:如果直接使用官网下载的jdk的安装包,这一步安装包会自动帮你做了
因为我既想要使用最新的jdk版本,体验Java语言的各种新特性,又想保留jdk8,毕竟又很多现有项目还是依赖 jdk8.
所以我计划安装 openjdk 的 21和8两个版本(当前最新jdk版本是jdk21)。
具体安装时,我使用了 brew 进行安装,两个版本的jdk默认都安装在了 /usr/local/Cellar/ 目录下
分别是:/usr/local/Cellar/openjdk@8/1.8.0-392
和 /usr/local/Cellar/openjdk/21.0.1
同时 brew 会在 /usr/local/opt 目录下帮你自动创建软连接,类似这样:
/usr/local/opt/openjdk@8 -> ../Cellar/openjdk@8/1.8.0-392
注:安装路径可以使用 brew list openjdk
命令来查看
安装完毕后,按照说明再创建软连接,具体在 /Library/Java/JavaVirtualMachines
目录下的创建各个jdk版本的软连接,链接到实际jdk的安装目录
具体的命令为
sudo ln -sfn $(brew --prefix)/opt/openjdk@8/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-8.jdk
sudo ln -sfn $(brew --prefix)/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-21.jdk
注:
此时在命令行输入 java -version
命令就可以得到 openjdk-21 的相关信息,因为默认会找 /Library/Java/JavaVirtualMachines 下最新版本的那个jdk
我想要动态切换jdk版本,经过网上一番查找后,大致发现有两个方案:
1)使用alias别名配置JAVA_HOME变量
2)使用类似 jEnv 这样的工具去管理多个版本的jdk
由于我想简单一点,以便清楚的知道切换jdk过程中都发生了什么,于是选择了第一个方案,使用alias别名配置 JAVA_HOME变量。
这一方案主要的思路是,首先默认配置一个 JAVA_HOME变量 指向我想要默认的jdk安装目录,比如默认指向jdk21,当我要切换到 jdk8 时,只需要在命令行中输入 jdk8 这个别名,就可以自动执行
export JAVA_HOME=$JAVA_8_HOME
, 这样来切换当前的 JAVA_HOME 变量,要切换回jdk21的时候再 export JAVA_HOME=$JAVA_21_HOME
为了方便起见,直接在 ~/.bash_profile 文件中分别配置两个版本jdk安装目录,以及重设 JAVA_HOME 的别名,默认 JAVA_HOME 设置为 jdk21 的目录。
同时按照多数教程的教学,添加了 JAVA_HOME 到 PATH 变量
# [Java]
export JAVA_8_HOME="/Library/Java/JavaVirtualMachines/openjdk-8.jdk/Contents/Home"
export JAVA_21_HOME="/Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home"
alias jdk8="export JAVA_HOME=$JAVA_8_HOME"
alias jdk21="export JAVA_HOME=$JAVA_21_HOME"
export JAVA_HOME=$JAVA_21_HOME
export PATH=$JAVA_HOME/bin:$PATH
注意:因为默认的shell是zsh,所以还需要在 ~/.zshrc 文件中配置,这样配置才会在 zsh 的shell中生效。
source ~/.bash_profile
打开命令行,开始测试配置,默认 java -version
显示 jdk21,使用 echo $JAVA_HOME
命令打印的结果也是 jdk21 的结果,没问题。
开始切换jdk版本,使用设置好的别名 jdk8 切换,切换后,先看了下变量值有没有改过来,使用 echo $JAVA_HOME
命令打印的结果是 jdk8 的结果,这里也没问题。
然后,java -version
显示的还是 jdk21 的版本信息,切换失败了!!!
这个时候脑子有点懵,如果这会我用 whereis java
查看一下就会发现,此时已经有两个 java 命令,除了 /usr/bin/java 还有一个是 /Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/man/man1/java.1
而首先找到的java命令是后者,可以使用 which java
命令查看。
那么之后单纯切换JAVA_HOME的值,根本不会有啥效果。因为/Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/man/man1/java.1 这个 java命令就是写死的 jdk21 的java
但我实际的操作是,先把配置中的 JAVA_HOME 变量设置的默认值注释掉了
# [Java]
export JAVA_8_HOME="/Library/Java/JavaVirtualMachines/openjdk-8.jdk/Contents/Home"
export JAVA_21_HOME="/Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home"
alias jdk8="export JAVA_HOME=$JAVA_8_HOME"
alias jdk21="export JAVA_HOME=$JAVA_21_HOME"
#export JAVA_HOME=$JAVA_21_HOME
export PATH=$JAVA_HOME/bin:$PATH
而这样之后,结果就是可以正常切换jdk版本了,在当时的我看来就很奇怪:默认不配置JAVA_HOME时可以成功切换jdk版本,配置了反倒不行了?但切换的命令本质不也是去配置了JAVA_HOME变量吗?
我陷入了深深的自我怀疑。。。
就在此时,我才注意到,不论我切换到那个jdk版本,使用 which 命令查看到 java 都是 /usr/bin/java。
这引起了我的注意,难道/usr/bin/java这个 java 是每次都会去动态的读取 JAVA_HOME 的属性,找到当前的 jdk 路径,所以可以动态切换jdk版本
此时大致明白了思路,但是还没弄懂他是怎么动态读取的,于是去网上查找,最终找到一篇:https://stackoverflow.com/questions/21964709/how-to-set-or-change-the-default-java-jdk-version-on-macos/44169445#44169445
看完后弄明白了:/usr/bin/java 是 macOS 系统内部的一个命令,它的行为模式有:
1 如果本机没有安装 jdk (这里包括下面的是否安装jdk均指的是以 /Library/Java/JavaVirtualMachines 为主的目录下有或者没有jdk),那么会报错:
The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.
2 如果有安装一个或者多个jdk,那么会去找最新版本的jdk去使用,同时需要注意会排除掉 Contents/Info.plist 被修改为 Contents/Info.plist.disabled 的jdk
3 如果配置了 JAVA_HOME 变量,那么这个优先级时最高的,会直接去找 JAVA_HOME 变量指向的jdk,此时无论 Contents/Info.plist 是否 disabled 都会用指向的jdk
随后,我也意识到了,上面那个奇怪问题的原因:默认不配置 JAVA_HOME 时可以成功切换jdk版本,配置了反倒不行了?但切换的命令本质不也是去配置了JAVA_HOME变量吗?
当我在 ~/.bash_profile 中没有配置 JAVA_HOME 默认值时,添加到 PATH 中的目录其实是 /bin,此时去命令行中执行 java 命令,那么就还是 /usr/bin/java,所以可以根据 JAVA_HOME 动态切换jdk版本
当我配置了 JAVA_HOME 后,添加到 PATH 中的目录就变成了 /Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/bin
,
并且由于配置
J
A
V
A
H
O
M
E
/
b
i
n
在前
‘
e
x
p
o
r
t
P
A
T
H
=
JAVA_HOME/bin 在前 `export PATH=
JAVAHOME/bin在前‘exportPATH=JAVA_HOME/bin:$PATH`,所以命令后中执行 java 命令就是直接执行 /Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/bin/java 了,
自然就无法切换 jdk 版本了。
至此,我的核心疑惑就解决了。
至于上面为什么总是说 /usr/bin/java 找到是以 /Library/Java/JavaVirtualMachines 为主的目录,可以参考:https://gist.github.com/hogmoru/8e02cf826c840914a8ed93fd418ed88e
这位大佬用 opensnoop 命令去追踪了 java -version
的执行过程,他的 mac 系统相对老一点,可能安全检查没那么严格。
我尝试了在我的系统下做同样的事情,报了系统安全的错误,所以就没再去尝试,但大致原理应该就是这样了。
其他目录没试验过,但 /Library/Java/JavaVirtualMachines 目录一定会去找。
另外,网上很多文章提到的 /usr/libexec/java_home -V
命令,查看的也是 /Library/Java/JavaVirtualMachines 下有哪些 jdk
/usr/libexec/java_home
可以查看当前java命令的home目录,而不一定是 /usr/bin/java 当前指向的 java
一般情况下,这两者是一致的,但有多个 java 命令时,就会和最先的那个 java 命令保持一致。
which cmd-name
查看名称为 cmd-name 的命令的具体路径。whereis cmd-name
查看 cmd-name 的命令的所有路径列表。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。