ARTICLE DETAIL

资讯详情

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

Linux下JDK卸载安装与环境变量配置指南:多版本切换与故障排查

Linux下JDK卸载安装与环境变量配置指南:多版本切换与故障排查 作为在Linux上折腾过无数回JDK的人我太清楚这个场景了新项目要求必须把JDK从8升到17结果卸载完旧版本才发现java -version还在倔强地输出老版本号或者装好了新JDK一执行source /etc/profile整台机器命令直接瘫痪又或者明明按教程配了JAVA_HOME但重启之后一切恢复原样。如果你也在这个问题上反复碰壁这篇文章就是给你写的。我把CentOS系和Ubuntu系两种主流发行版上从摸底排查、分类卸载、选型下载到环境变量配置、多版本切换、常见故障排查的完整链路都过一遍里面的步骤和坑都是我实际踩过、验证过的照着操作就能少走一大段弯路。1. 为什么卸载JDK在Linux上老是卸不干净先回答一个很多人没想明白的问题为什么在Linux上卸载JDK这么容易留尾巴根本原因在于JDK在Linux里的存在方式是分散的。和Windows下一个安装目录加上注册表的集中模式完全不同Linux上的JDK通常同时以三种形式扎根在系统里二进制文件、环境变量脚本、软链接与alternatives机制。你在网上能找到的教程通常只教其中一种姿势而实战里这三样往往叠在同一个系统里只处理其中一两处就等于没卸干净。1.1 三种安装方式留下的三种痕迹按我在生产环境里见过的实际情况JDK的安装方式基本可以归为三类各自留下的清理负担完全不同。安装方式文件分布位置卸载时主要打扫战场rpm/yum/apt 包管理器安装/usr/lib/jvm、/usr/bin、/usr/share等标准目录由包数据库跟踪卸载包本体、清理依赖、处理alternatives记录tar.gz手动解压安装通常在/usr/local/java或/opt下的自定义目录删除整个目录、清理软链接、清理环境变量从容器镜像或已编译发行版中直接拷贝目录集中在自定义位置但常被多个应用直接引用删除目录前必须确认没有进程和服务脚本还在引用它我遇到最多的情况是同一台机器上同时存在包管理器装的OpenJDK 8和一个手动解压的JDK 11。两条链路叠加之后java命令被alternatives软链接指向其中一个而JAVA_HOME却指向另一个。这种情况下你单独卸掉其中一个系统表现会非常分裂——有的应用能用有的应用立刻报错。1.2 假卸载背后的环境变量与软链接残留所谓假卸载就是指你执行了rpm -e或者apt removeJDK本体确实删了但/etc/profile、~/.bashrc里还留着export JAVA_HOME/usr/lib/jvm/java-1.8.0...这样的行PATH里也还残留着旧JDK bin目录的路径。更隐蔽的是软链接残留/usr/bin/java这个文件本身往往不是一个真实二进制而是指向/etc/alternatives/java的软链接后者再指向真实的JDK目录。卸载JDK后第一个软链接可能还在只是最终目标变成了不存在的文件。结果就是你敲java -version要么返回一个莫名其妙的错误要么因为PATH兜底机制找到了另一个版本输出了一个阴魂不散的旧版本号。这也是为什么很多人在卸载后固执地认为这系统没救了。其实系统没病只是残留没清干净。2. 动手卸载前先摸清这台机器上到底有几个JDK卸东西之前得先知道有什么。这个步骤看似多余却是整个流程里最能省时间的一环。我见过不少人一上来就rm -rf一个目录结果都不知道系统里还装着另一个JDK最后定位问题花了几个小时。2.1 用三条命令锁定当前生效的JDK先在当前终端里确认正在用的JDK到底是哪个全套命令如下java -version which java ls -l $(which java) readlink -f $(which java)我拿一台CentOS 7上的典型输出举例$ java -version openjdk version 1.8.0_362 OpenJDK Runtime Environment (build 1.8.0_362-8.b08.el7_9.x86_64) OpenJDK 64-Bit Server VM (build 25.362-b08, mixed mode) $ which java /usr/bin/java $ ls -l /usr/bin/java lrwxrwxrwx. 1 root root 22 1月 7 10:00 /usr/bin/java - /etc/alternatives/java $ readlink -f /usr/bin/java /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362-8.b08.el7_9.x86_64/bin/java第一行告诉你当前实际生效的版本最后一行告诉你这个版本真正的家在哪。这两条信息是后续所有判断的基准。2.2 扫描所有副本包列表加目录盲查接下来要把机器上所有JDK副本都找出来尤其是那些潜伏的手动安装包。按发行版分别执行RHEL/CentOS/Fedora系rpm -qa | grep -iE jdk|java-Debian/Ubuntu系dpkg -l | grep -iE jdk|openjdk然后再做一次目录层面的盲扫防止有解压包没被包管理器记录过find /usr /opt /home -name java -type f 2/dev/null find /usr/local /opt -maxdepth 3 -type d -iname *jdk* 2/dev/null第一局命令是全盘范围的谨慎使用。第二局命令限定在常见安装目录和深度速度快、噪音少。把所有结果汇总后你会得到一张JDK分布全景图哪些由包管理器管、哪些是手动解压、哪些只是软链接的中间状态。这张图就是下一步卸载的依据。2.3 卸载前评估依赖和备份动手之前花两分钟确认两件事能避免一次事故级操作。第一确认没有正在运行的Java进程依赖当前JDKps -ef | grep java | grep -v grep如果看到Tomcat、Spring Boot、Elasticsearch等进程还在跑先停下来确认服务归属后再决定是否卸载。第二给当前JDK做个快速备份。对虚拟机可以直接做快照没有快照就压缩一下目录tar czf /tmp/jdk_backup_$(date %F).tar.gz /usr/lib/jvm这步不丢人。我干运维这些年一次快照救回整个环境的情况遇到过太多次了。别觉得自己操作熟练就不需要备份JDK这种被全链路依赖的基础组件回滚能力比一次成功重要得多。3. 按安装方式拆解卸载步骤rpm、deb、tar.gz各有各的归宿现在进入正题。卸载动作本身不难难的是针对不同安装方式做对应的完整清理。我按三种主流安装方式分别给出操作路径最后再补一个统一的环境变量清扫方案。3.1 RPM系rpm -e 与 yum remove 的取舍在CentOS/RHEL/Rocky/AlmaLinux这类系统上先用前面的命令确认包名列表然后执行卸载rpm -qa | grep -i jdk # 卸载指定包注意包名要写全 rpm -e java-1.8.0-openjdk-1.8.0.362-8.b08.el7_9.x86_64但这里我强烈建议优先用yum remove或dnf remove而不是裸rpm -e理由很实际yum会自动处理依赖关系。裸rpm -e卸载后那些当初被自动装进来的依赖包会变成孤儿长时间堆积在系统里。当然yum remove也可能顺带把依赖它的其他软件一起删掉所以执行前仔细阅读它列出的将移除的包清单看到有非JDK相关的东西就停下来确认。卸载完成后执行一下yum autoremove # 或 dnf autoremove清理被自动安装且不再被任何包需要的孤儿依赖。这个命令有时会误伤所以同样要先看输出清单。3.2 Debian系apt purge 清理得更彻底Ubuntu/Debian系的包管理逻辑略有不同先查看已安装的Java/JDK相关包apt list --installed | grep -iE jdk|openjdk然后推荐用带--purge参数的卸载方式apt remove --purge openjdk-11-jdk openjdk-11-jre-headlessremove只删二进制和库文件purge会把配置文件也一并清掉。在Debian系上软件卸载后留下的配置文件往往是各种诡异报错的来源所以我默认都用purge。装过JDK的包数量可能不止两三个比如openjdk-17-jdk-headless、openjdk-17-jre之间还有依赖关系显示在列表里的都处理掉。操作完同样执行apt autoremove清理孤儿依赖。3.3 tar.gz 解压安装删目录只是第一步手动解压包卸载的难点不在删目录而在软链接和环境变量的二次清理。删目录本身很简单rm -rf /usr/local/java/jdk1.8.0_362但删完之后必须检查以下三个地方缺一个都算没卸干净。第一手工建立的软链接。当时在/usr/bin或/usr/local/bin下用ln -s建过java、javac、jar的话要逐个删除rm -f /usr/bin/java /usr/bin/javac /usr/bin/jar如果当初是通过update-alternatives注册的用update-alternatives --remove来移除update-alternatives --remove java /usr/local/java/jdk1.8.0_362/bin/java update-alternatives --display java第二查看update-alternatives的当前状态确认没有悬空记录update-alternatives --display java第三搜索并清理所有指向该目录的环境变量引用。用这一条命令把可能的藏身之处一网打尽grep -rn jdk1.8.0_362\|JAVA_HOME\|CLASSPATH /etc/profile /etc/profile.d/ ~/.bashrc ~/.bash_profile ~/.profile /etc/environment 2/dev/null搜出来的内容逐条查看该注释的注释该删的删。3.4 环境变量与alternatives残留的统一清扫不管哪种安装方式最后都要做一遍环境变量和alternatives的大扫除。这里我强调一个操作顺序先清理包和目录再清软链接和alternatives最后清环境变量反过来会非常难受。原因很简单——如果把环境变量先删了你想确认当前shell用的到底是哪个java都没了线索排查问题会失去抓手而先清包和目录环境变量里的路径就会明确地无效化你一眼就能看出哪些行该删。清理的核心命令就一条grep -n JAVA_HOME /etc/profile /etc/profile.d/*.sh ~/.bashrc ~/.bash_profile ~/.profile 2/dev/null再检查一下/etc/alternatives有没有指向无效目标的悬空链接ls -l /etc/alternatives/java 2/dev/null update-alternatives --display java 2/dev/null做完这一步这台机器上的旧JDK才算真正寿终正寝。4. 装新JDK前必须确定的版本、厂商和下载渠道卸载完只是前半场装新JDK同样有一堆决策要做。很多人直接跳进下载环节结果在版本选择、厂商授权、下载速度这几个点上翻车。4.1 LTS版本怎么选从JDK 8到JDK 21版本选型这件事环境不同结论也不同。我按2025年这个时间点的实际生态情况给出一张对照表版本生态地位典型场景我的建议JDK 8 (LTS)老牌主力Hadoop、Spark等大数据生态兼容性最好存量老项目、绑定大数据组件版本的环境能迁移就迁移官方维护已进入后期JDK 11 (LTS)大量中间件的最低版本门槛ZGC和HTTP Client转正框架版本锁定了11的企业项目处于过渡期新项目不推荐JDK 17 (LTS)Spring Boot 3、主流云原生框架的默认要求新项目首选之一2025年最稳妥的选择JDK 21 (LTS)虚拟线程正式版、分代ZGC、Record模式等新特性追求新特性、高并发场景正在加速普及新项目可以放心上关于LTS这顶帽子我要多说一句Java的版本节奏是每半年一个功能版本LTS才是真正长期受支持的版本。像JDK 12、JDK 16这种非LTS版本发布几个月后官方就不再提供免费更新。生产环境选型时别只看新特性优先认准LTS。顺便回应一下热搜里的jdk降级到17这种需求——降级的原因通常是某个框架、某个中间件刚升级后不兼容高版本JDK或者高版本JVM参数在低版本下不被识别。降级本身做法和老版本安装没什么区别重点是装完后要把根因搞清楚版本降下来了问题是否真的解决了别让版本成了背锅侠。4.2 官网、国内镜像和系统源怎么选确定版本之后下载渠道的选择直接决定你安装环节顺不顺利。发行版自带源yum install java-17-openjdk或apt install openjdk-17-jdk。最省事和系统包管理深度集成装完自动注册alternatives维护成本最低。缺点是你拿到的不是Oracle官方JDK而是OpenJDK的发行版编译产物。清华/阿里云/华为云镜像站国内访问速度快提供OpenJDK、Adoptium等产物的镜像。适合需要手动解压安装、对JDK构建来源有要求的场景。Oracle官网官方JDK功能最全但下载需要注册登录且新版Oracle JDK的许可协议NO-FEE、OTN在商用场景下有明确限制务必读清楚再用。SDKMAN适合开发机和CI环境一条命令就能装、切换任意版本但对服务器上线这种一次性部署场景反而多了一层概念负担。我个人的倾向是生产环境的物理机或云服务器优先用发行版自带OpenJDK维护最省心测试环境或容器环境用SDKMAN或内网镜像手动解压不要在服务器上硬趴Oracle官网很多人卡在登录验证这一步白白消耗半天时间。5. 环境变量配置的少踩坑方案文件选择、写法与验证环境变量配置是整个JDK安装过程里翻车率最高的环节。原因很简单网上教程各说各话有写/etc/profile的、有写~/.bashrc的、有写/etc/environment的看着都能用实际上作用域、加载时机都有差异。把差异搞清楚就不会再犯配置了不生效或者一改就崩全局这种错误。5.1 把环境变量写进 /etc/profile.d 而不是 /etc/profile先说结论我最推荐的方式是在/etc/profile.d/下新建一个独立脚本比如java.sh。/etc/profile在bash登录时被读取是公共区域。很多程序的安装脚本会往这个文件末尾追加内容时间一长这个文件会被各种用途的配置塞满维护性和可阅读性都很差。一旦改错一个字符影响的是所有用户的所有登录会话。而/etc/profile.d/目录是/etc/profile启动时自动遍历加载的一个扩展目录所有.sh文件都会被依次执行。把JDK配置放到一个独立文件里好处是职责单一、排查方便、卸载时直接rm掉这个文件就清理干净了不需要在大型配置文件里小心翼翼地找行删行。这是Linux下管理环境变量的最佳实践不只是JDK任何软件建议都按这个思路来。5.2 JAVA_HOME和PATH的标准写法及三个常见误区以JDK 17解压到/usr/local/java/jdk-17为例标准的配置写法是cat /etc/profile.d/java.sh EOF export JAVA_HOME/usr/local/java/jdk-17 export PATH$JAVA_HOME/bin:$PATH EOF两个关键点一个是顺序一个是别配CLASSPATH。PATH的顺序直接影响命令解析优先级。export PATH$JAVA_HOME/bin:$PATH是把新JDK路径放在前面这样在交互shell里执行java时会优先命中新版本。如果你写成export PATH$PATH:$JAVA_HOME/bin而系统/usr/bin下恰好存在旧版本那么旧版本会一直压制新版本java -version怎么验证都是旧的。我见过很多人明明装好了新JDK最后发现是这个顺序问题导致的排查了很久。第二点是JDK 9模块化之后不要再配置CLASSPATH。很多老教程会让你写这样一行export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar但JDK 9以后dt.jar、tools.jar这两个文件已经不存在了。配了这行编译时类路径里多了一个幽灵jar反而导致各种找不到类的诡异报错。现代开发用Maven、Gradle或IDE管理类路径根本不需要手动配CLASSPATH。这个观念害人不浅我身边真实案例某同事的编译报错排查了一整天最后就是这行残留配置惹的祸。第三个隐藏坑是export语句的等号两侧不能有空格。export JAVA_HOME /path在bash里会被解析成执行export命令并传入三个参数报错信息模棱两可新手很难一眼看穿。5.3 配置完成后的一条龙验证写完配置后让配置在当前会话立即生效source /etc/profile.d/java.sh也推荐直接新开一个终端窗口让shell自己加载这样能验证重启后配置是否持久化。然后执行java -version javac -version echo $JAVA_HOME which java echo $PATH一条条检查java -version输出版本号是否符合预期javac -version是否和java版本一致JAVA_HOME是否非空且指向正确which java路径是否指向新JDK的bin目录。这几个输出全部正常才算配置成功。如果发现重启后变量丢失基本可以确定是写进了非持久化的位置比如直接在交互shell里export的没有落盘。如果发现java对了但javac不对多半是alternatives里只注册了java没有注册javac。6. 多版本JDK共存时的两种切换思路生产环境和开发机上多版本共存几乎是刚需。手头三四个项目各自要求不同的JDK版本总不能每次换项目都改环境变量重开终端。这里介绍两个我用下来最顺手的思路。6.1 update-alternatives 管理全局软链接update-alternatives是Debian系和RHEL系都提供的软链接管理工具。它的原理是这样/usr/bin/java是一个软链接指向/etc/alternatives/java而后者再被update-alternatives管理指向真正的JDK二进制。切换版本时只需要改/etc/alternatives/java的指向无需改动PATH。注册两个版本到alternativesupdate-alternatives --install /usr/bin/java java /usr/local/java/jdk-17/bin/java 1700 update-alternatives --install /usr/bin/java java /usr/local/java/jdk-1.8.0_362/bin/java 1800最后的数字是优先级数值大的默认生效。切换版本时执行update-alternatives --config java会弹出交互菜单让你输入编号选择版本。注意这只管理你注册过的命令如果注册时只注册了java那javac、jar、keytool这些命令不会跟着切。要完整管理就得把这些命令也依次注册一遍。所以这个方案适合命令级切换比较直观。6.2 JAVA_HOME指向current软链接的做法另一个我更喜欢、在服务器上用得更多的方案是建立一个当前版本的软链接名字就叫current。先把所有JDK版本整整齐齐放在/usr/local/java/目录下ls /usr/local/java/ jdk-1.8.0_362/ jdk-17/建立current链接指向当前默认版本ln -s /usr/local/java/jdk-17 /usr/local/java/current然后在环境变量脚本里永远只写这个current路径cat /etc/profile.d/java.sh EOF export JAVA_HOME/usr/local/java/current export PATH$JAVA_HOME/bin:$PATH EOF以后要切版本只需要执行rm -f /usr/local/java/current ln -s /usr/local/java/jdk-1.8.0_362 /usr/local/java/current这个方案的好处非常明显环境变量脚本、Systemd服务单元、应用启动脚本里永远只有current这一个路径切换版本就是改一个软链接一秒完成且不会出现改了脚本但其他脚本没跟着改的遗漏问题。升级JDK也一样解压新版本到目录改一下current即可对应用无感。配合第5节讲的做法这套方案是我这些年用得最舒服的。如果你还需要Maven、Gradle等工具跟随JDK切换只要它们读取的是JAVA_HOME变量就都会自动跟着current走这是这个方案最大的可扩展性价值。7. 卸载与安装过程中高频出现的四个故障排查链路最后这一大章我把这些年遇到的高频坑和完整排查思路整理出来。每个问题都不是直接给答案而是给出一步步定位的链路希望你下次遇到时不只会修还能会查。7.1 旧JDK卸载后 java -version 仍输出旧版本这是卸载场景中排名第一的灵异事件。明明执行了卸载命令JDK目录也删了为什么java -version还有输出完整排查链路# 第一步看java命令到底在哪 which java # 第二步看它是不是软链接指向哪里 ls -l $(which java) # 第三步看软链接最终指向什么目标文件是否存在 readlink -f $(which java) # 第四步确认是否还有残留的JDK包 rpm -qa | grep -iE jdk|java- # CentOS系 dpkg -l | grep -iE jdk|openjdk # Ubuntu系 # 第五步查环境变量里是否还有旧JDK路径 echo $PATH grep -n JAVA_HOME\|jdk /etc/profile /etc/profile.d/*.sh ~/.bashrc 2/dev/null我见过最典型的情况是系统里原本有OpenJDK 8和手动解压的JDK 8两套卸载包管理器版本后/usr/bin/java的软链接虽然被调整了但PATH里手动解压版本的bin目录还排在前面于是java命令从PATH中找到了那个残留目录里的二进制继续输出旧版本号。按上面五步走完定位到具体来源再按第3章的清理路径处理即可。这里补充一个容易忽略的细节修改环境变量后当前shell的PATH缓存可能还是旧值因为bash会缓存命令路径。执行一下hash -r清除命令哈希表能避免明明改了配置、但当前终端里怎么测都是旧命令的假象。7.2 改坏profile导致大量命令找不到的救援流程这个故障足够吓人但也是最好处理的。典型原因是有人把export PATH$JAVA_HOME/bin:$PATH写成了export PATH$JAVA_HOME/bin少了$PATH。source之后当前会话的PATH被整个覆盖ls、vi、grep、cat全部消失看起来系统废了。救援流程冷静执行# 第一步用绝对路径打开文件修复 # 因为ls等命令已经不在PATH里了必须用绝对路径调起来 /usr/bin/vi /etc/profile.d/java.sh # 或者直接用sed批量替换修复 /bin/sed -i s#export PATH/usr/local/java/jdk-17/bin#export PATH$JAVA_HOME/bin:$PATH# /etc/profile.d/java.sh # 第二步修复后用绝对路径启动一个子shell /bin/bash # 第三步检查PATH是否恢复正常 echo $PATH如果是通过systemd服务的shell环境被改坏不用慌直接重开一个新SSH连接或者用系统控制台登录新会话会重新读取配置。这个坑的根本教训是修改任何profile文件之前先备份尽量使用/etc/profile.d/下的独立小文件不要在profile脚本里写复杂循环和条件判断这种高风险逻辑。7.3 压缩包乱码、校验失败与glibc依赖缺失linux 解压文件乱码这个话题常年是搜索热榜常客在JDK下载场景里也经常出现。如果你下载的是.tar.gz格式解压后出现乱码文件的概率极小因为tar格式本身处理文件名编码的机制比较规范。真遇到乱码多半是压缩包下载不完整或者被中断。这时候别想太多先校验文件完整性echo 官方发布的SHA256值 下载的文件名 | sha256sum -c -校验不通过直接重新下载别在一个坏压缩包上浪费时间。如果你下载的是.zip格式在中文locale环境下解压文件名乱码是常态根因是zip格式里记录了文件名的编码方式而Windows下生成的zip通常是GBK/CP936。处理方式是指定解码unzip -O GBK jdk-17_linux-x64_bin.zip -d /usr/local/java/但更省事的办法是直接选择.tar.gz格式从源头上避开这个编码问题。另一个隐藏坑是JDK 17以上版本在某些精简版或国产化系统上运行报错提示找不到某个共享库。这时用ldd查看动态依赖是否完整ldd /usr/local/java/jdk-17/bin/java缺什么库就通过包管理器补什么。这不是JDK坏了是基础运行库不完整。7.4 服务脚本报找不到jdk的定位方法找不到jdk这个现象经常发生在普通用户或服务环境里典型表现是脚本里echo $JAVA_HOME输出为空。排查链路分几步第一步确认该用户环境下是否加载了profile.d脚本。通过在shell里执行bash -l重新以登录shell方式启动再看echo $JAVA_HOME是否有输出。第二步确认脚本是否有执行权限。虽然放在/etc/profile.d/下通常不要求x权限但如果配置文件被手动移动过权限位可能不对顺手看下ls -l /etc/profile.d/java.sh第三步重点排查Systemd服务场景。Systemd服务默认不会加载/etc/profile和/etc/profile.d/里的环境变量这是很多人部署Spring Boot时百思不得其解的问题——明明终端里echo $JAVA_HOME正常服务启动却找不到JDK。解决办法是在Service单元文件的[Service]段显式声明[Service] EnvironmentJAVA_HOME/usr/local/java/current EnvironmentPATH/usr/local/java/current/bin:/usr/bin:/bin或者把环境变量写进启动脚本里source一下。这是终端正常、服务异常类问题最典型的成因。关于JDK的卸载与安装我能分享的实战经验差不多就是这些。最后说一个我个人的习惯不管在哪台机器上JDK安装这件事我都会按照解压到统一目录、profile.d里只写一行JAVA_HOME、current软链接管理版本这套固定流程来做。它看起来朴素但把改环境变量导致全线服务起不来这种凌晨事故的触发机会降到了零。希望这篇整理也能让你的Linux环境里少一些JDK带来的折腾。
返回列表