news 2026/9/30 1:27:35

Linux下多JDK版本安装与切换实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux下多JDK版本安装与切换实践指南

1. 为什么需要在Linux上管理多个JDK版本

1.1 多版本并存的典型场景

先说一个我自己的经历。接手一个老项目时,它还在用JDK 8跑着Spring Boot 2.x,代码里一堆javax.*包,换到JDK 11就各种报错。而旁边的新项目已经上了JDK 17,用了不少Spring Boot 3的特性。装两个JDK?那是必然的。更头疼的是,还需要在两个项目之间来回切换开发,每次切项目都要重新配置环境变量,搞了两次就烦了。

这其实是很多开发者的真实处境。基于常见的开发环境经验,多JDK并存的需求主要来自这几种场景:

  • 不同历史时期的项目并存,老项目锁死JDK 8/11,新项目用JDK 17/21。
  • 测试某个依赖或框架在不同JDK版本下的兼容性,需要快速切换对比。
  • 学习或复现别人的代码,对方的运行环境和你本机不一致。
  • 使用一些特定工具时,工具本身对JDK版本有硬性要求。

也就是说,多JDK并存不是“要不要”的问题,而是“怎么管理”的问题。如果处理不好,轻则java -version对不上,重则项目起不来,甚至系统自带的工具都被你改崩了。

1.2 切换版本的核心思路

这里先讲清楚一个关键概念:Linux系统本身并不知道也不关心你安装了几个JDK,它只知道你给它指定的那个。你通过环境变量JAVA_HOME告诉系统JDK在哪里,通过PATH告诉系统去哪找java可执行文件。

所谓的“切换JDK版本”,本质上是同一件事的三种操作方式:

  1. 修改JAVA_HOME指向不同的JDK目录。
  2. 修改PATH中$JAVA_HOME/bin的顺序或指向。
  3. 让java、javac、jps等命令通过符号链接指向当前需要的版本。

只要这三件事做对了,版本就切换成功了。很多初学者搞不清楚的是:为什么环境变量改了,java -version还是老的?因为终端会话启动时已经读取了环境变量,你改完文件后没有执行source,或者当前shell的PATH缓存还没刷新。

明白了这个底层逻辑,后面的所有方案就都顺理成章了。本质上就是在“改环境变量”和“改符号链接”这两条路上做文章。

2. 安装JDK的几种方式与版本选择

2.1 选择哪个JDK发行版

说到JDK,很多人脑子里蹦出来的第一个词是“Oracle JDK”。但说实话,日常开发和测试我更推荐OpenJDK或Eclipse Temurin(Adoptium项目)。理由很实际:免费、开源、持续更新、和主流构建工具兼容性好。Oracle JDK在17之前需要商业许可,而且从17开始虽然许可放宽了,但很多团队还是规避风险选了OpenJDK。

如果你在用的是国内云服务器,还可以考虑几个方便下载的镜像源。比如清华TUNA、阿里云镜像、华为云镜像,下载速度快且稳定。对于生产环境,建议紧跟LTS版本,像JDK 8、11、17、21都是当前主流的LTS版本。

具体到发行版选择,给大家一个参考:

发行版提供商适用场景备注
OpenJDKOracle/社区日常开发、生产最普遍,兼容性好
TemurinAdoptium生产、容器环境提供完整的JRE/JDK构建包
ZuluAzul嵌入式、特殊平台支持ARM、多架构
Oracle JDKOracle商业需求、官方认证注意许可条款
Dragonwell阿里超大规模生产有针对性优化

我平时用Temurin最多,因为它在Docker镜像里基本是标配,而且Adoptium官网下载页的tar.gz包都是免安装的,解压就能用,很适合本文的场景。

2.2 手动解压安装(tar.gz)的方式

这种方式最干净,也最好控制,推荐大家优先使用。步骤很简单:

# 1. 创建统一的目录,把JDK集中放在一起 sudo mkdir -p /usr/local/java cd /usr/local/java # 2. 下载JDK(以Temurin 17为例,假设已经下载了tar.gz包) ls jdk-17*.tar.gz # 3. 解压 sudo tar -xzf jdk-17*.tar.gz # 4. 重命名,方便区分版本(关键步骤,别偷懒) sudo mv jdk-17.0.12 jdk-17 # 5. 在另一个目录解压JDK 8 sudo tar -xzf jdk-8*.tar.gz sudo mv jdk-8u412 jdk-8

之所以放在/usr/local/java下,是因为/usr/local是Linux标准的第三方软件安装目录,权限和目录结构的约定都比较清晰。当然,如果你没有sudo权限,放在自己的home目录也可以,比如~/java/jdk-17,后续配置方式完全一样。

重命名这个步骤看起来简单,但特别关键。如果不重命名,你会面对/usr/local/java/jdk-17.0.12和/usr/local/java/jdk-1.8.0_412这种结构,后续写脚本、改环境变量时容易手误。统一命名成jdk-17、jdk-8这种简洁的名称,管理成本低得多。

2.3 通过包管理器安装(apt/yum)的限制

很多初学者会直接用包管理器安装:

sudo apt update sudo apt install openjdk-17-jdk

这样确实省事,JDK会被自动安装到/usr/lib/jvm/目录下,并且包管理器会自动配置好默认的java命令。但问题在于,当你装完JDK 8再装JDK 17,系统只会默认把其中一个设为默认版本,切换需要靠update-alternatives来操作。

包管理器安装的好处是自动处理依赖、环境变量配置比较规范;坏处是你对JDK目录的控制力弱了一些,尤其是如果你想在同一个目录下管理多个自解压的JDK,包管理器的方式就显得有些笨重。

我的建议是:如果只是装一个JDK,随便哪种方式都行。如果明确有多版本需求,直接手动解压,后续切换的自由度最大。当然,包管理器安装的JDK也可以通过update-alternatives --config java切换,这个我们后面会详细讲。

3. 配置环境变量的核心原理

3.1 JAVA_HOME和PATH到底是怎么工作的

先打一个比方。把系统比作一栋大楼的物业管理中心,你在楼里上班(运行Java程序)。管理中心怎么知道你在哪个办公室?它需要一张通讯录,上面写着“java程序的办公室在3楼”。JAVA_HOME就是这张通讯录里的条目,指向的是你的“办公室”(JDK安装目录)。

而PATH是另一张更有权力的通讯录,它告诉系统:当你输入java命令时,要去哪些目录依次寻找这个命令。如果JAVA_HOME指向JDK 17,但PATH里/usr/local/java/jdk-8/bin排在前面,系统找到的还是JDK 8的java。

所以,配置环境变量的本质是确保两件事同时正确:

  1. JAVA_HOME指向你当前想用的JDK目录。
  2. PATH里$JAVA_HOME/bin排在所有其他JDK目录之前。

到这里大家应该明白了,切换版本并不一定非要改JAVA_HOME本身,你只要把PATH里的查找顺序调整好,效果是一样的。

3.2 ~/.bashrc、~/.profile、/etc/profile到底该改哪个

这个问题几乎每个Linux新手都会踩坑。解释一下几个文件的区别:

  • /etc/profile:系统级的,对所有用户生效,登录时加载。需要sudo权限才能改。
  • ~/.profile:用户级的,只对当前用户生效,登录时加载(但如果你用的是bash非登录shell,它可能不加载)。
  • ~/.bashrc:用户级的,只对当前用户生效,每次打开一个新的bash终端都会加载。这是平时改环境变量最常用的文件。

如果只是给自己用,改~/.bashrc就够了。改了~/.bashrc后,运行source ~/.bashrc立即生效,或者直接重开一个终端。注意不要同时把环境变量写到/etc/profile和~/.bashrc里,两个地方都定义时,优先级容易出现混淆。

我自己习惯的做法是:只改~/.bashrc,其他文件不碰。因为我并不希望所有用户(包括root)都强制使用某个特定的JDK版本。如果你确实需要系统级默认版本,再考虑/etc/profile或/etc/environment。

3.3 一份干净的环境变量配置模板

下面是我常用的配置模板,放在~/.bashrc底部:

# JDK切换配置 export JAVA_HOME=/usr/local/java/jdk-17 export PATH=$JAVA_HOME/bin:$PATH

需要注意一个细节:PATH=$JAVA_HOME/bin:$PATH把JDK的bin目录放在最前面,然后接原有的PATH。这样就保证了你输入java时,系统优先找到这个路径下的版本,而不是其他目录里的旧版本。

配置完后需要验证:

source ~/.bashrc echo $JAVA_HOME java -version which java

如果输出符合预期,说明JDK已经生效。如果which java显示的是/usr/bin/java而不是/usr/local/java/jdk-17/bin/java,说明有其他配置干扰,需要用type -a java查看所有找到的路径。

4. 多版本切换的几种方案与实操

4.1 方案一:手动修改环境变量(最简单但最笨)

这个方案最容易理解,每次切换就是改~/.bashrc中的JAVA_HOME,然后source一下。

sed -i 's|export JAVA_HOME=.*|export JAVA_HOME=/usr/local/java/jdk-8|' ~/.bashrc source ~/.bashrc java -version

切换回17也是同理。但这个方法有两个明显的缺点:一是每次要用sed或手动打开文件编辑,操作繁琐;二是容易改错,比如某个字符输错了,整个环境变量就崩了。

所以,这个方法只建议在”我只需要偶尔切一次“或者“临时测试某个版本”时用。日常频繁切换的话,你会被自己烦死的。

4.2 方案二:自定义shell函数(推荐日常切换)

既然手动改文件太麻烦,那就写个函数,把切换动作固化下来。在~/.bashrc底部加上:

# JDK切换函数 function switchjdk() { case "$1" in 8) export JAVA_HOME=/usr/local/java/jdk-8 ;; 11) export JAVA_HOME=/usr/local/java/jdk-11 ;; 17) export JAVA_HOME=/usr/local/java/jdk-17 ;; *) echo "Usage: switchjdk {8|11|17}" return 1 ;; esac export PATH=$JAVA_HOME/bin:$PATH echo "Switched to JDK $1" java -version }

注意,这里export PATH=$JAVA_HOME/bin:$PATH有一个坑:每次执行函数,都会把$JAVA_HOME/bin再次插到PATH最前面。如果之前PATH里已经有了旧的JDK路径,你会看到PATH不断变长,从而引起混乱。更严谨的写法是先把PATH里旧的JDK路径清理掉,再重新设置。但实际测试下来,只要$JAVA_HOME/bin始终排在最前面,旧的路径即便存在也不会被优先命中,所以简单场景下这个坑可以不纠结。

用法很简单:

switchjdk 8

切换后用java -version验证。这个方法最大的优势是快,而且不用改文件、不用重启终端,非常适合日常开发。

如果想再进一步加固,可以加一个当前版本的提示符显示,比如修改PS1:

export PS1="\u@\h [JDK:\$(basename $JAVA_HOME)] \w$ "

这样每次输入命令时都会看到当前用的哪个JDK,避免切错版本导致一些诡异的问题。

4.3 方案三:基于符号链接的切换(适合系统级统一控制)

如果你需要多个用户共用一套JDK,或者需要让java命令在系统级别统一指向某个版本,可以用符号链接的方式。

首先把系统默认的java命令指向某个链接:

sudo ln -sf /usr/local/java/jdk-17/bin/java /usr/bin/java sudo ln -sf /usr/local/java/jdk-17/bin/javac /usr/bin/javac

切换时,只需要重新替换链接:

sudo ln -sf /usr/local/java/jdk-8/bin/java /usr/bin/java sudo ln -sf /usr/local/java/jdk-8/bin/javac /usr/bin/javac

这种方式的好处是即使某些脚本写死了/usr/bin/java,也能生效。缺点是你必须使用sudo,每次切换要敲两行命令,而且对JAVA_HOME没有直接帮助——很多Java工具(比如Maven、Gradle)是看JAVA_HOME的。

所以,我的经验是:符号链接用于解决“某些脚本或者systemd服务直接调/usr/bin/java”的场景,而环境变量用于解决“Shell会话中开发工具”的场景。两个配合起来才完整:先改JAVA_HOME,再改符号链接。这也是为什么官方推荐用update-alternatives,它能同时管理这两件事。

4.4 方案四:update-alternatives 系统级切换

Debian/Ubuntu系统其实自带了一个管理命令工具,就是update-alternatives。它本质上是一个符号链接的管理器,但可以做得更规范。

首先要注册我们手动解压的JDK:

sudo update-alternatives --install /usr/bin/java java /usr/local/java/jdk-17/bin/java 1 sudo update-alternatives --install /usr/bin/java java /usr/local/java/jdk-8/bin/java 2 sudo update-alternatives --install /usr/bin/javac javac /usr/local/java/jdk-17/bin/javac 1 sudo update-alternatives --install /usr/bin/javac javac /usr/local/java/jdk-8/bin/javac 2

最后那个数字是优先级,数字越大优先级越高。如果想给某个JDK更高的默认优先级,给它一个更大的数字就行。

切换时执行:

sudo update-alternatives --config java

系统会把所有已注册的JDK列出来,让你输入序号选择。选了之后,/usr/bin/java会自动指向对应的JDK路径。

这个方法适合系统级的版本管理,尤其是服务器上有多用户时。但它仍然不能直接解决JAVA_HOME的切换问题——update-alternatives管理的是命令链接,不是环境变量。所以用这个方法时,建议再加上JAVA_HOME的手动设置,或者配合前面的switchjdk函数一起用。

4.5 方案五:SDKMAN 现代化的多版本管理工具

如果你觉得上面的方式都太“手工”,那必须试试SDKMAN。它跟nvm(Node Version Manager)、pyenv类似,是一个针对JVM生态的版本管理工具。SDKMAN不仅能管理JDK,还能管理Maven、Gradle、Spring Boot CLI等,非常强大。

安装SDKMAN:

curl -s "https://get.sdkman.io" | bash source ~/.bashrc

安装JDK(以17为例):

sdk install java 17.0.12-tem

查看当前可用版本:

sdk list java

切换版本:

sdk use java 17.0.12-tem

SDKMAN会在你的用户目录下创建.sdkman/candidates/java/目录,所有版本都放在那里,然后用符号链接current指向当前版本。它改的是你Shell会话里的JAVA_HOME和PATH,并不会动系统级的/usr/bin/java。

SDKMAN还支持项目级配置:在项目根目录创建.sdkmanrc文件,内容写上java=17.0.12-tem,然后执行sdk env init,以后每次进入这个目录时执行sdk env activate,它会自动切换到该文件规定的版本。如果你经常同时开发多个版本的项目,这个功能会让你感觉非常舒服。

不过需要提醒一点:SDKMAN对网络有要求,官方源在下载JDK时可能比较慢,需要配置镜像或更换下载源。如果你在国内,建议提前设置SDKMAN_CANDIDATES_API指向国内镜像源,否则安装过程可能卡在下载环节。

5. 版本切换过程中的常见问题与排查技巧

5.1 问题速查表

症状可能原因解决思路
java -version 版本不对PATH顺序不对或未source查看which java、type -a java,调整PATH
JAVA_HOME 一直为空配置写在错误的文件里检查 ~/.bashrc、~/.profile,确认source过
java和javac版本不一致两个命令分别指向不同JDK用which javac定位,统一符号链接或环境变量
Maven/Gradle仍用旧版本Maven读的不是JAVA_HOME确认mvn -version显示的环境变量
切换后IDEA等工具失效IDE缓存了旧路径在IDE里重设JDK路径或清除缓存
脚本中调/usr/bin/java符号链接未更新用 update-alternatives 或手动 ln -sf

这个表格基本覆盖了我这些年踩过的绝大部分坑。但这里展开讲几个最典型的。

5.2 排查实例:java和javac版本不一致

有一次我在一台机器上装好了JDK 17,java -version也显示17,但运行javac -version愣是显示8。找了好久才发现,java来自/usr/local/java/jdk-17/bin/java,而javac来自/usr/bin/javac——之前系统里用apt装的OpenJDK 8留下了这个命令,它没被PATH的JDK 17覆盖。

这种问题的排查路径是:

which java which javac

如果两个命令不在同一个目录,说明它们分别来自不同的安装方式。解决办法是把/usr/bin/javac的符号链接重新指到目标JDK上:

sudo ln -sf /usr/local/java/jdk-17/bin/javac /usr/bin/javac

或者干脆把所有相关命令都通过update-alternatives注册一遍,一劳永逸。

5.3 排查实例:切换JDK后Maven仍然用旧版本

Maven、Gradle这类构建工具启动时,会去找JAVA_HOME来决定自身运行在哪个JDK上。如果你用switchjdk改变了Shell环境,但Maven还是旧版本,优先检查:

mvn -version

如果输出里显示的Java版本不对,查看输出中的Java home路径。Maven在读取环境变量时,会先看系统的JAVA_HOME,再看自己bin/mvn脚本里的默认配置。某些情况下你改了~/.bashrc,但Maven脚本是从另外的位置读取的。

解决办法:在启动Maven前显式指定:

export JAVA_HOME=/usr/local/java/jdk-17 export PATH=$JAVA_HOME/bin:$PATH mvn -version

如果还是不行,检查/etc/environment文件,看是否有全局的JAVA_HOME覆盖了你的配置。

5.4 实战心得:如何避免切换版本时把系统搞崩

最后分享几个经验教训。

不要轻易动/usr/bin/java。很多Linux系统工具(比如Hadoop、Elasticsearch)会依赖这个路径下的Java。如果你用符号链接把它指向了一个新版本,但这些工具脚本并没有适配新版本,它们可能直接报错。所以,切换/usr/bin/java时,务必确认所有常用的系统服务都是兼容的。

用自解压JDK时,给每个JDK单独建一个目录,不要把两个版本混放在同一个目录里。哪天你升级JDK,直接把整个旧目录删掉换新的,干净利落。

不要频繁地在~/.bashrc和/etc/profile之间来回切换配置。坚持只用其中一种方式,否则配置会非常混乱,排查问题时也会非常痛苦。

还有一个容易被忽视的细节:有些项目用.mvn/jvm.config或 Gradle 的gradle.properties指定了项目级JDK版本,这些配置优先级高于环境变量。也就是说,即使你终端里切到了JDK 17,项目可能仍然在JDK 8上运行。遇到这种情况,不是环境变量的问题,而是项目自身的构建配置需要改动。

5.5 防御性检查技巧

最后养一个好习惯:每次切换完JDK,不要只看java -version,建议再跑一条测试命令确认整个工具链都是通的:

java -version javac -version echo $JAVA_HOME which java which javac mvn -version 2>/dev/null || echo "Maven not found"

把这五条命令绑成一个别名或函数,比如jdkcheck,每次切换后直接调用,一目了然:

alias jdkcheck='java -version; javac -version; echo "JAVA_HOME=$JAVA_HOME"; which java; which javac'

这个习惯帮我省了不知道多少排查时间。环境变量毕竟是个“隐性问题”,多一步检查,比等问题浮现再去翻找要高效得多。

6. 多JDK版本管理的最佳实践总结

聊到这里,整套多JDK管理的方案已经全部铺开了。如果让我给一个明确的推荐组合,我会这样选:

在个人开发机上,优先使用手动解压 + shell函数(方案二),配合SDKMAN作为备选工具。在多人共享的服务器上,优先使用/usr/local/java统一存放 +update-alternatives管理命令链接,同时规定所有用户的JAVA_HOME指向的路径。

从实际维护经验来看,最稳妥、最不容易出问题的做法是:JDK固定在/usr/local/java/下,命令链接用update-alternatives控制,环境变量用统一的一段脚本管理,并且每个用户都会加载同一个切换函数脚本。这样团队里所有人用的是同一套JDK管理规范,不会出现“你机器上能跑,我机器上跑不了”这种版本差异问题。

其实多JDK切换这件事,说难不难,但说简单也不简单,关键还是把底层逻辑搞清楚:JAVA_HOME负责告诉工具链“我要用哪个JDK”,PATH负责告诉Shell“我要去哪儿找命令”,update-alternatives负责管理系统级符号链接。三者各司其职,配合起来就能做到随意切换且不互相干扰。

根据我个人的经验,真正卡住大家的往往不是安装步骤本身,而是环境变量生效范围和优先级的问题。把一个文件里配置对了不算结束,还要验证它是否在所有需要读取的进程(Shell、构建工具、IDE)里生效。多花两分钟做防御性检查,后面就能省下两小时的查错时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 1:27:35

微信小程序商城+Java后台源码解析:从本地跑通到二次开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:27:16

CentOS 7 安装配置全攻略:从镜像下载到 JDK、MySQL、Nginx 部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:26:48

STM32C5驱动IIS3DWB:I2C通信与寄存器配置实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:26:38

Sentinel规则持久化:Nacos替代内存存储的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:26:38

索尼VAIO P酷袋本深度体验:便携、性能妥协与二手淘机避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:26:25

昂科烧录器适配HVC5221D:车规电机驱动器量产烧录全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华