Mac系统命令行中的Java到底在哪?

Mac系统命令行中的Java到底在哪?

系统版本

我的系统版本: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

注:

  1. 这里使用 sudo 获取root权限是因为要在 /Library/Java/JavaVirtualMachines 目录下新建文件
  2. ln 是创建软连接到命令(软连接可以简单理解为一个快捷方式)
    3)$(brew --prefix) 获取 brew 安装的前缀,这里相当于 /usr/local

此时在命令行输入 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 命令保持一致。

总结

  1. 命令行中运行的命令,只要不是在当前目录下的,哪都是在环境变量 $PATH 配置的路径列表里的某个路径里的,否则找不到这个命令
  2. 命令行中的命令路径是那个,可以使用 which cmd-name 查看名称为 cmd-name 的命令的具体路径。
    2.1 大多数情况下,指向的就是命令本体,
    2.2 还有可能指向的是一个软连接,
    2.3 最后的可能就是类似 /usr/bin/java 这样的系统代理命令(你看不到软连接,但是代理内部会去找真实的命令,一般都是去某些固定目录寻找)
  3. 如果怀疑一个命令有多个配置,那么可以使用 whereis cmd-name 查看 cmd-name 的命令的所有路径列表。
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值