恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Linux 环境 JDK 安装配置与多版本切换实践指南
首页
资讯中心
/
Linux 环境 JDK 安装配置与多版本切换实践指南
Linux 环境 JDK 安装配置与多版本切换实践指南
发布时间:2026/10/2 2:59:30
搞Java开发的十有八九都在Linux上部署过服务。JDK装得好不好直接决定后面Tomcat、Spring Boot容器能不能稳。说它难吧无非就是解压、配变量、验证三步网上教程一抓一大把说它简单吧生产环境里因为JDK环境变量配置失败、多版本切来切去切出问题、下载个安装包还被人忽悠去装了一堆全家桶的我见得实在太多了。这篇内容不打算写成那种抄来抄去的文档我把Linux装JDK从选版本到排错整个链条里所有值得讲的细节一次说透全部基于实际运维经验直接能照着操作。先说清楚适合谁看。刚接触Linux的实习生、被分配了服务器部署任务的后端开发、还有想梳理JDK管理思路的运维都能在里头找到对应自己那段的东西。核心围绕三件事怎么选对版本和下载渠道、怎么配环境变量配得又稳又合理、多版本共存时怎么切换不翻车。1. 安装前的核心认知版本、架构和渠道很多人一上来就搜“jdk下载官网”点进去发现Oracle的下载页面还要登录、还要勾选协议麻烦得要命。然后就去找各种“jdk镜像网站”结果要么是全家桶安装包要么是某个老版本被人转存的三手资源解压出来连个SHA256校验都没有。我建议先把认知理清楚再动手。1.1 OpenJDK还是Oracle JDK别再被“官方”绑架了绝大多数业务场景OpenJDK和Oracle JDK在运行时行为上几乎没有差异。Java 11以后Oracle JDK和OpenJDK的构建流程已经高度趋同以前那些收费差异更多体现在商业授权和更新服务上。你在Linux上部署Spring Boot、跑个Nacos、挂个KafkaOpenJDK完全够用。以前有个老说法“生产环境必须用Oracle JDK”这其实是JDK 8时代的一种历史惯性。现在很多云厂商的基础镜像默认就是OpenJDK跑得好好的。选择逻辑很简单如果你没有特殊组件对Oracle JDK的特定版本有硬性依赖直接装发行版自带的OpenJDK包或从正规镜像站下载OpenJDK构建版省心、更新也方便。真正值得认真考虑的倒是另一个问题如果你公司内部用了比较老的框架比如某些祖传项目还锁在JDK 8上那就别去追新版本了。这也是“jdk降级到17”这类搜索为什么会这么多——不是新版本不好而是老项目不认。1.2 版本选择按LTS主线走准没错版本选择这件事我一直主张一个原则能用LTS就绝不碰非LTS。JDK目前能选的LTS主线有8、11、17和21这几条。JDK 8是宝刀不老Spring Boot 2.x时代的主流很多老系统的默认底线。JDK 11是很多中大型公司从8往上迁的第一站因为它是第一个长期支持版里具备较完整模块化特性的版本。JDK 17是目前新项目的主力Spring Boot 3.x要求最低就是17各种新特性比如增强的伪随机数生成器、密封类、switch模式匹配都在这个版本上落地。JDK 21是最新的LTS虚拟线程正式登场对高并发IO密集型服务有实打实的提升。我的建议是新项目直接上17老项目能留8就留8不要为了追新强行把老项目升到17会牵扯出Spring、MyBatis、Netty一整套依赖兼容问题。20.0这个中间版本除非你有明确理由否则别碰。1.3 确认系统架构x86_64和aarch64别搞混下载JDK安装包之前先用一条命令确认系统架构这是很多人提都没提过的关键一步。uname -mx86_64绝大多数Intel和AMD的服务器都是这个对应下载x64或amd64架构的JDK。aarch64华为鲲鹏、飞腾这类ARM架构服务器是这个对应下载aarch64架构的JDK。i386/i68632位的古董机器现在很少见了对应x86 32位包但基本不建议再用了。我曾经见过有人在一台ARM架构的鲲鹏服务器上硬解压了一个x86_64的JDK包然后配置环境变量怎么配都报错最后一看是架构不匹配。这种问题非常隐蔽因为解压命令是正常的java -version却提示无法执行二进制文件一眼看过去像是权限问题实际是架构不兼容。# 错误示例bash: /usr/local/jdk/bin/java: cannot execute binary file: Exec format error这条报错如果搜出来大半都是架构选错了。1.4 下载渠道包管理器优先镜像站次之官网最后安装JDK的渠道优先级我排一个序供参考优先级渠道适用场景优点缺点1发行版包管理器apt/yum/dnf生产环境、标准部署版本经过发行版测试升级维护方便版本通常滞后2正规镜像站清华源、阿里源等需要特定版本下载速度快资源可信版本不够全3Oracle官网需要最新版或商业支持一手官方资源下载流程繁琐需登录关于“jdk镜像网站”这类搜索我要多说一句网上有一堆打着镜像名义的个人站下载到的安装包往往绑了乱七八糟的东西。我建议只认两类来源一类是操作系统自带的软件源另一类是高校或者云厂商维护的开源镜像站。其他来路不明的压缩包解压前先做SHA256校验能有效拦截一部分被篡改的风险。2. 环境准备与旧环境清理在很多生产机器上系统自带的OpenJDK和以前手动装过的多个JDK版本常常混在一起。直接装新的PATH一乱运行java命令可能指向一个你根本不知道的版本。这一节先把准备工作做扎实。2.1 检查系统当前已安装的JDK先看看系统里现在到底有哪些Java相关的可执行文件和目录。# 查看当前默认java版本 java -version # 查看java全路径 which java # 查看所有在PATH中的java whereis java # 查看系统已安装的java相关软件包Debian/Ubuntu dpkg -l | grep -i jdk dpkg -l | grep -i openjdk # 查看系统已安装的java相关软件包RHEL/CentOS rpm -qa | grep -i jdk rpm -qa | grep -i openjdk这一步不是可选的。很多环境变量配置失败的案例根源就是系统里安装了多个JDK而PATH中默认的那条路径指向了一个旧版本或者一个被删除的残留目录。先把家底摸清楚后续操作心里才有底。2.2 删除还是保留生产环境的取舍策略如果系统里存在不需要的旧JDK要不要删我的建议分两种情况如果旧JDK是通过包管理器安装的 OpenJDK比如在Debian系里被安装为openjdk-11-jdk而你接下来要手动安装JDK 17那最好卸载掉避免update-alternatives的管理和你手动配置的环境变量互相干扰。卸载命令示例sudo apt -y purge openjdk-11-jdk openjdk-11-jre-headless注意这个purge不只是删除软件包还会尝试删除配置和related文件力度比remove大生产环境慎用先确认这台机器上没有正在运行的服务依赖它。如果旧JDK是当初手动解压放在某个目录下的那么只需要找到它、确认没有服务引用、然后删除对应目录即可。# 找到那个目录假设是通过历史配置得知或被PATH引用 ls -l /usr/local/java/ ls -l /opt/java/然后备份、删除或者在处理前先重命名为.bak观察几天再彻底清理。生产环境我一般先重命名不敢直接删。2.3 清理残留的JAVA_HOME配置有一种很气人的情况JDK已经删了但/etc/profile、/etc/environment、~/.bashrc里的JAVA_HOME配置还在导致每次登录终端都报错。比如你打开终端会看到-bash: /usr/local/jdk/bin/java: No such file or directory这种提示虽然不影响登录但会让排查问题的人非常烦躁。建议在安装新JDK之前把这些文件里的旧JAVA_HOME、CLASSPATH、PATH相关行全部清理干净统一后续再配置。3. 三种安装方式实操对比与逐步演示安装JDK的方式归纳下来主要就三种包管理器安装、源码包手动安装、容器化方式。我逐一拆解重点放在应用范围最广的手动安装式。3.1 包管理器安装最快但版本受限的方式Debian系Ubuntu、Deepin用aptsudo apt update sudo apt -y install openjdk-17-jdkRHEL系CentOS、Rocky、AlmaLinux用dnf或yumsudo dnf -y install java-17-openjdk-devel包管理器方式的最大优点是简单升级也方便。但它有两个天然问题第一版本由发行版仓库决定可能会滞后官方好几个月想要特定版本号就得另想办法第二它装出来的目录结构遵循发行版约定JDK主目录在/usr/lib/jvm/下面和你后来手动解压的JDK路径逻辑不太一样多版本共存时容易绕晕。我不建议把包管理器安装作为唯一installation但它非常适合快速搭建开发环境或跑一跑测试脚本。生产环境用这套方式装一个版本倒是完全可行关键是后续JAVA_HOME的定位方式要变一下。3.2 手动解压安装最通用、最可控的方式这是我要重点讲的也是网上教程最混乱的一段。很多教程会让你去Oracle官网下载tar.gz然后解压、配变量一个坑连着一个坑。我以一个完全可复现的例子来演示。步骤一准备安装目录我习惯把JDK统一放在/usr/local/下面这是Unix系统约定俗成的第三方独立软件目录。sudo mkdir -p /usr/local/java步骤二下载JDK.tar.gz包假设你的系统是x86_64需要装JDK 17可以从正规镜像站获取tar.gz包。这里以Adoptium的release地址为例它的构建质量和社区认可度都不错。cd /tmp wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz注意下载后一定要校验文件完整性。JDK包被传输中断或者被篡改解压时症状千奇百怪而且很难定位。# 官方会提供对应的sha256sum比对一下 echo 你查到的sha256值 OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz | sha256sum -c -步骤三解压并移动到目标目录sudo tar -zxvf OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz -C /usr/local/java/ ls /usr/local/java/ # 解压后通常是个类似 jdk-17.0.99 的目录这里有个细节tar解压后目录名带着版本号比如jdk-17.0.99。这个目录名建议保留如果你以后要切换多个版本看到目录名就知道版本非常直观。为了后续配置方便我一般会建一个不带版本号的软链接指向当前主版本sudo ln -sfn /usr/local/java/jdk-17.0.99 /usr/local/java/default为什么这样做因为你在/etc/profile里的JAVA_HOME写绝对路径写死版本号以后升级JDK就得改配置文件写成JAVA_HOME/usr/local/java/default以后升级时只需要换软链接的指向一行命令搞定。步骤四验证二进制文件能执行在配置环境变量之前先直接执行解压出来的java命令确认文件本身没坏、依赖库不缺/usr/local/java/jdk-17.0.99/bin/java -version这一步往往能提前暴露出架构不匹配、libc版本过低这类问题。检验不过去就不用继续往环境变量上折腾了。3.3 容器化安装隔离干净但别忽略基础镜像现在很多微服务场景直接用Docker镜像跑Java应用宿主机上甚至不需要安装JDK。这是合理的因为容器镜像内部已经包含了JRE/JDK。这样宿主机层面根本不装JDK从根源上避免了多版本冲突。但容器化方式有个坑很多人写的Dockerfile是这么干的FROM ubuntu:22.04 RUN apt-get update apt-get install -y openjdk-17-jdk ENV JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64这当然没毛病不过镜像体积会膨胀得很厉害。更推荐的玩法是直接用现成的JRE基础镜像比如Eclipse Temurin的镜像把你的应用jar包和JDK良好的隔离性一次都解决了。FROM eclipse-temurin:17-jre WORKDIR /app COPY target/app.jar /app/app.jar ENTRYPOINT [java, -jar, /app/app.jar]把宿主机上的JDK安装问题直接变成镜像内的JDK版本选择问题这实际上是现代云原生产业里更推荐的方式。4. 环境变量配置与验证核心中的核心环境变量配置之所以成为“jdk环境变量配置失败”这类热搜词的聚集地核心原因是大多数教程只告诉你“要配”却没讲清楚“为什么这么配”更没讲清楚“配置的生效机制”。我把这些一次性讲清楚。4.1 为什么需要JAVA_HOME和PATHJAVA_HOME是一个约定俗成的环境变量很多Java生态的中间件比如Tomcat、Maven、Gradle启动脚本都会读取它来定位JDK所在目录。只有两种方式能让它存在一种是你在配置文件里手动export另一种是包管理器默认帮你加入/etc/profile.d/。PATH的作用则是让shell能在任何目录下直接执行java、javac而不需要输入完整路径。它的查找机制是当你在终端输入一个命令时shell会按照PATH中从左到右的路径顺序逐个目录查找这个命令找到第一个就执行。这就能解释为什么配了JAVA_HOME却没用——如果PATH中前面的路径里先找到了一个旧的java那么你配的JAVA_HOME根本不会被用到。用生活化的类比PATH相当于小区的“地图检索列表”标注了几栋楼目录小区门卫shell找人的时候从名单第一栋楼开始挨个找只要找到叫“java”的人就把人带出来。你新来的java专家JDK住在名单最后面前面几栋楼已经有同名的人门卫根本轮不到去找你那位专家。4.2 推荐配置写法与逐行解释最常见的配置文件是/etc/profile它会在用户登录时执行对所有用户生效。我在生产环境更推荐写独立的/etc/profile.d/java.sh文件因为系统加载profile时会自动读取/etc/profile.d/下所有.sh文件这样管理JDK配置时一行代码就知道该改哪个文件也不容易误碰系统主profile文件。sudo vim /etc/profile.d/java.sh写入以下内容export JAVA_HOME/usr/local/java/default export PATH$JAVA_HOME/bin:$PATH第一行把JAVA_HOME指向前面建的软链接目录这是整个配置的锚点。第二行把JDK的bin目录放到PATH的最前面。注意这里我强烈建议用$JAVA_HOME/bin因为以后你切换到另一个JDK版本这个相对路径写法能自动适配。如果写死/usr/local/java/jdk-17.0.99/bin升级后就会指向一个不存在的目录。关于网上流传的CLASSPATH配置这是我的保留意见现代JDK完全不需要手动配置CLASSPATH。JDK 1.5之后javac/java已经能自动定位所有核心类和库手动设置CLASSPATH不仅多余还会干扰程序对classpath的自主判断引发许多莫名其妙的NoClassDefFoundError。如果你依赖某个第三方程序要求设置CLASSPATH通常是那个程序自身打包有问题。4.3 配置的生效机制source、重启和弹起shell配置文件改完了环境变量并不会自动在当前终端生效。你需要重新登录或者执行source命令。source /etc/profile.d/java.sh这里必须要说清楚一个很多人忽视的坑source只在当前终端会话中生效。如果你是通过SSH工具连接服务器source之后这个终端里java命令正常了但新开一个SSH窗口又执行一遍java还是会报错。但这不代表你没配好而是因为新窗口的登录流程还没重新读取profile文件。出现这种情况要么重新登录当前SSH要么在新的SSH连接里再source一次。大多数环境变量“配置完当时好用、第二天又不行了”的现象基本都是这个原因。验证环境变量是否生效echo $JAVA_HOME echo $PATH java -version javac -version正常情况下输出/usr/local/java/default /usr/local/java/default/bin:/usr/local/sbin:/usr/local/bin:... openjdk version 17.0.9 2023-10-17 ... javac 17.0.9如果java -version输出的不是你认为应该有的版本请执行which -a java它会列出PATH中能找到的所有java路径你基本一眼就能看出是哪个目录下的旧java占了先。4.4 是否要区分root和其他用户很多教程直接让改/etc/profile然后配好之后用普通用户登录发现java命令也生效了这没问题因为profile对所有用户生效。但有一种特殊情况你用普通用户通过非登录shell方式执行命令比如某些CI/CD流水线通过ssh执行远程命令时可能不会加载/etc/profile而是只加载~/.bashrc。要保持稳妥我建议在~/.bashrc里也补一行同样的配置export JAVA_HOME/usr/local/java/default export PATH$JAVA_HOME/bin:$PATH这两个文件的生效区别简单说profile是登录shell加载的bashrc是交互式非登录shell加载的。两边都配能覆盖绝大多数使用方式但注意不要在两边配出矛盾值不然又以PATH顺序做最终裁判了。5. 多版本JDK切换与daily运维实战生产环境很多机器上同时存在JDK 8和JDK 17两套环境一边是老项目要跑一边是新服务要上。这一节讲讲共存和切换的问题。5.1 用update-alternatives管理系统JDK如果你的JDK是通过apt/dnf安装的Debian系提供的update-alternatives是终极管理工具sudo update-alternatives --config java sudo update-alternatives --config javac执行后会显示一个列表There are 2 choices for the alternative java (providing /usr/bin/java). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1071 auto mode 1 /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1071 manual mode 2 /usr/lib/jvm/java-8-openjdk-amd64/bin/java 1061 manual mode Press enter to keep the current choice[*], or type selection number:输入对应数字即可切换系统默认java这种方式本质是把/usr/bin/java这个符号链接切到不同版本上。注意update-alternatives切换的是/usr/bin/java和/usr/bin/javac如果你手动配置的JAVA_HOME优先级更高那么切换后shell里的java命令仍然不一定会变。这时候必须把JAVA_HOME也改成与update-alternatives一致的路径才能保持完全对齐。5.2 手动解压的多版本管理方案如果你更喜欢手动解压我的思路是每个版本一个目录用一个软链接指向当前主版本作为default再备一个软链接专门给老项目用。/usr/local/java/ ├── jdk-8u202-linux-x64 # 老版本 ├── jdk-17.0.99 # 新版本 ├── default - /usr/local/java/jdk-17.0.99 └── jdk8 - /usr/local/java/jdk-8u202-linux-x64老项目的启动脚本里可以通过JAVA_HOME/usr/local/java/jdk8这种指定的方式强行覆盖/etc/profile.d/java.sh里的default设置实现一个系统两套JDK并行。我实际维护过一台机器上面同时跑着JDK 8的旧支付服务和JDK 17的新网关就用这个方案稳定得很。5.3 “jdk降级到17”这类需求到底怎么处理“jdk降级到17”这个说法我猜大概率不是从21或者更高的版本降而是从项目中某个临时用的新版本降回17这个LTS或者反过来说项目中当初不小心装了版本号太新的JDK比如21或23而框架依赖跟这个新版本不兼容需要统一回到17。处理方式其实就两步第一步把当前默认版本切换成17。如果你用的是update-alternatives执行第上面的config命令选择17如果你手动解压管理只需要把default软链接指向17那个目录。sudo unlink /usr/local/java/default sudo ln -s /usr/local/java/jdk-17.0.99 /usr/local/java/default第二步清理并验证。这一步的目的不是真的清理而是让所有相关进程知道变更。检查正在运行的Java相关服务确认它们是随后续启动正常使用新版本即可。注意一点一个已经在运行的Java进程在运行时使用的是它启动瞬间解析到的JDK目录里的原生库改环境变量不会对运行中的进程有任何影响。所以降级或升级后请务必重启服务进程这是很多人漏掉的关键动作。5.4 常见问题与排查技巧实录把日常运维里最容易踩的坑整理成一张速查表问题现象可能原因解决思路bash: java: command not foundPATH中无java路径检查配置文件路径是否写对先执行/usr/local/java/default/bin/java -version排除JDK本身问题No such file or directorybin/java文件损坏或架构不匹配uname -m查看架构全部核对下载包与系统是否匹配java -version显示老版本PATH中旧路径在前which -a java查看所有位置调整PATH顺序或删旧目录source后当前会话正常新开SSH不行配置写在bashrc而不是profile.d确认写入 /etc/profile.d/java.shtomcat启动时提示找不到JAVA_HOME启动脚本没有读取你的环境变量Tomcat的catalina.sh会读catalina环境变量在启动脚本里显式export JAVA_HOME中文注释编译后乱码文件编码和javac默认编码不一致javac加-encoding UTF-8或者在eclipse/IDEA里统一字符集编译报错Source option 7 is no longer supportedJDK版本过高和高版本javac对旧语法支持变化确认使用哪一版JDK编译项目pom或build.gradle里指定source/target兼容版本cannot execute binary file64位系统跑了32位包换成x64/amd64架构的JDK包再补充两个排查实录这两个案例都是真实遇到过的案例一某台Ubuntu 20.04服务器终端敲java -version能输出但是java -jar app.jar时总是报UnsupportedClassVersionError。排查后发现系统里同时存在两个OpenJDK/usr/bin/java指向的是11但JAVA_HOME手工指向到了17的目录。jar包是用17编译的运行时却被11的java拉起来。解决办法是把两者对齐要么让 /usr/bin/java 指向17要么让JAVA_HOME指向11。案例二有人在/etc/profile里写PATH/usr/local/java/bin:$PATH这里写的是bin目录而不是JDK的bin目录导致PATH前面出现了一个不存在的路径。这不影响其他命令但任何依赖PATH检索的脚本都必须跳过这个不存在的目录效率降低之余还可能导致某些依赖PATH顺序的工具误判。规范写法是$JAVA_HOME/bin且确保JAVA_HOME指向有效。5.5 检查JDK是否被其他服务引用的小技巧生产环境改动JDK之前先看一眼哪些服务在用Javaps -ef | grep java | grep -v grep这条命令列出所有正在运行的Java进程你会看到它们的启动命令大概长这样root 12345 1 10 08:00 ? 00:05:12 /usr/local/java/jdk-17.0.99/bin/java -jar /opt/app/gateway.jar进程命令里的JDK路径其实就是它启动时读取到的JAVA_HOME。这就非常直观地告诉你如果改环境变量哪些服务会受影响哪些不会。6. 收尾经验与几个提升效率的小技巧写到这安装与配置主流程已经全部走完了。最后分享几个实用经验都是我实际使用过程中沉淀下来的对你的日常使用会有很大帮助。技巧一不要把JAVA_HOME写死在多个文件里我见过不少服务器/etc/profile、/etc/environment、~/.bashrc、/etc/profile.d/java.sh里都写了JAVA_HOME但每个写的内容还不一样。这是灾难的根源。我的标准做法是只在/etc/profile.d/java.sh写一套JAVA_HOME和PATH其他文件一律不碰。如果某个应用需要特定版本在对应应用自己的启动脚本里设置局部JAVA_HOME即可。统一配置入口出问题时的排查难度能下降一个量级。技巧二维护一个JDK版本目录说明文件在/usr/local/java/下放一个简单的说明文件不花几分钟但非常救命sudo vim /usr/local/java/java-versions.txt内容大致写清楚每个目录对应的版本、发布日期、从哪个渠道下载的、重点服务引用了哪个。半年后你回来看这台服务器这份文档比你再跑一遍./bin/java -version快得多。技巧三新机器快速装JDK的极简思路如果你在新机器上只想用一个稳定的版本不去管多版本的事我最快的路径总结如下先uname -m确认架构然后用包管理器装一次对应版本的OpenJDK最后把JAVA_HOME指向/usr/lib/jvm/java-17-openjdk-amd64或你安装后的实际目录。整个过程五分钟搞定不用下tar.gz不用处理软链接不需要在/etc/profile.d里反复试错。等以后有了多版本需求再切换到手动解压方案。最后一点个人体会Linux上安装JDK这件事它的难点从来不在“解压”这个动作而在你对环境变量生效机制、PATH查找顺序、多版本共存的整体理解。把这些底层逻辑搞透了不管以后遇到什么疑难杂症比如Maven找不到JDK、Dockerfile构建时JAVA_HOME丢失、CI流水线上java命令时有时无你都能顺着环境变量的链路一步步追本溯源。这套排查方法论比记住任何一条命令都值钱。