ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux下JDK安装与环境变量配置全指南:从版本选型到多版本共存

Linux下JDK安装与环境变量配置全指南:从版本选型到多版本共存 上周帮同事排查一台新到的云服务器装了一下午的JDK。不是下载慢就是版本搞错好不容易装完环境变量又配不上java -version慢悠悠报出一个陌生的版本号。这场景估计很多Linux新手都经历过。JDK安装本身不难网上教程一搜一大把但问题在于很多教程只告诉你“敲这三行命令”不告诉你为什么敲结果碰到一点偏差就直接卡死。今天这篇我按自己的操作习惯把Linux环境下JDK安装从上到下捋一遍版本怎么选、两种安装方式各自适合什么场景、环境变量配置失败到底怎么排查以及多个JDK版本怎么共存。看完不说成为环境管理专家至少能独立搞定大多数Linux服务器和开发机的Java环境。1. 动手之前先把版本、发行版和CPU架构这三个问题想清楚很多人在安装失败之后才回头查教程其实大部分坑在下载之前就已经埋下了。装JDK的第一步不是wget而是先回答三个问题装哪个版本的JDK装Oracle JDK还是OpenJDK你的Linux系统和CPU架构到底是什么这三个问题没搞清楚后面每一步都是运气。1.1 版本选择不要只看“最新”要看“项目需要”我见过太多人一上来就装最新版JDK结果项目跑不起来才发现用的是Spring Boot 2.x要求Java 8。JDK版本的选择逻辑很简单先看项目构建文件再看LTS生命周期。如果你只是个人练习选最新的长期支持版本比如17或21就行如果是部署公司项目先打开pom.xml、build.gradle或者启动脚本确认maven.compiler.source、java.version这些字段。热搜词里能看到大量“jmeter 安装 jdk 8”“jdk降级到17”的需求本质上都是版本没规划好事后返工。目前主流的选择集中在8、11、17、21这几个LTS版本。8是老项目的常青树很多金融、传统企业项目还在用11是中间过渡主力17是过去几年新项目的事实标准21是较新的LTS适合尝鲜但要注意中间件兼容性。需要说明的是高版本JDK通常能编译运行低版本目标代码但反过来不行UnsupportedClassVersionError就是这么来的。所以如果只是为了启动一个老项目装JDK 8最稳妥如果是新项目直接上17不要用8去找罪受。还有一个点容易被忽略JVM参数和垃圾回收器的变化。JDK 8里常用的-XX:UseConcMarkSweepGC在JDK 14之后就被移除了如果你直接拿老的JVM参数去启动新JDK会直接报Unrecognized VM option。所以版本切换不是简单换一个路径还得顺带检查启动脚本里的JVM参数。1.2 OpenJDK还是Oracle JDK差别比想象中小我见过有人在论坛上吵“必须用Oracle JDK否则会被起诉”这种说法早就过时了。Oracle JDK从Java 17开始已经和OpenJDK采用了基本相同的源码两者在运行时行为上几乎没有差别绝大多数场景下用OpenJDK完全没问题。Linux发行版软件源里默认提供的也是OpenJDK用包管理器直接装省心省力。在国内下载OpenJDK建议别直接跑Oracle官网下载麻烦还慢可以用国内CDN或镜像站。常见的清华镜像、阿里云镜像以及腾讯软件源都提供OpenJDK的二进制包搜“jdk镜像网站”“jdk下载官网”能看到很多入口。下载时注意核对校验值.sha256文件这是很多人会跳过的步骤但包不完整导致的tar解压报错我已经见过不止一次。1.3 确认Linux发行版和CPU架构很多“装不上”其实是下载了错包Linux不是“一个系统”Ubuntu、CentOS、Rocky、Debian、openEuler这些都是Linux但它们包管理器不同、目录约定不同甚至内核版本都差很远。安装之前先跑两条命令# 查看发行版信息 cat /etc/os-release # 查看CPU架构和内核 uname -m这两条命令的输出决定了你下载哪个安装包、用apt还是yum/dnf。最常见的问题是CPU架构搞错云服务器普遍是x86_64也就是amd64但ARM平台的机器比如树莓派、某些ARM云主机架构是aarch64下载x64的包放到ARM机器上会直接报“无法执行二进制文件”。另外现在国产CPU和国产操作系统的组合也越来越多像麒麟、统信UOS这类系统底层可能不是x86架构安装前务必uname -m确认。这也是为什么我建议装Linux环境时养成“先查系统再动手”的习惯两条命令能省掉后面两小时排查。2. 两种主流安装方式包管理器优先手动解压兜底Linux下装JDK从操作路径上分成两大流派一种是用系统自带的包管理器apt、yum、dnf直接安装另一种是下载官方压缩包手动解压并配置。两种方式都能跑但适用场景完全不同。我的建议是能用包管理器就用包管理器手动方式留给特殊版本和特殊架构。2.1 包管理器安装Debian系和RedHat系命令完全不同Debian系Ubuntu、Debian本身和RedHat系CentOS、Rocky、AlmaLinux、Fedora的安装命令长得完全不像。Ubuntu/Debian上sudo apt update sudo apt install -y openjdk-17-jdk-headlessRedHat系上sudo yum install -y java-17-openjdk-devel注意几个细节。第一Ubuntu上区分openjdk-17-jdk-headless和无-headless的版本前者不包含图形相关组件体积更小适合服务器如果你是本地桌面开发直接装不带-headless的完整版。第二RedHat系安装的是java-17-openjdk-develdevel包里才有javac只装java-17-openjdk的话没有编译器后面编译Java代码会报找不到javac。第三包管理器安装完成后JDK会自动注册到系统的alternatives机制里这意味着如果你以后装了第二个JDK可以用update-alternatives来切换这个后面单独讲。包管理器这种方式的核心优势在于安装路径由系统统一管理Debian系默认放在/usr/lib/jvm/卸载干净升级方便而且软件源里的包经过发行版测试稳定性有保障。劣势是版本普遍偏保守软件源里可能只有某个LTS版本想装最新的21、25就得靠手动方式。2.2 手动解压安装时序、目录、软链接一个都不能少什么情况下必须手动装三种场景最典型软件源里没有你想要的版本包管理器提供的版本太旧机器是aarch64或者特殊CPU架构软件源里没有对应包。手动安装的核心思路是“下载解压—放到统一目录—做软链接—配置环境变量”。以下是我在新服务器上的一套完整操作以JDK 17为例# 1. 创建统一目录建议所有JDK都放这里 sudo mkdir -p /opt/java # 2. 解压到 /opt/java 下注意包名换成你自己的 sudo tar -zxvf jdk-17.0.10_linux-x64_bin.tar.gz -C /opt/java/ # 3. 创建无版本号的软链接方便以后切换 sudo ln -sfn /opt/java/jdk-17.0.10 /opt/java/current # 4. 写入环境变量新建 /etc/profile.d/java.sh 更整洁 sudo tee /etc/profile.d/java.sh /dev/null EOF export JAVA_HOME/opt/java/current export PATH$JAVA_HOME/bin:$PATH EOF # 5. 让环境变量立即生效 source /etc/profile.d/java.sh # 6. 验证 java -version javac -version这里有几个非常想强调的点。第一强烈建议不要直接把解压出来的目录命名为jdk而是保留版本号目录加一个current软链接。这样以后升级JDK只需要把新版解压进去然后改一下current软链接指向即可环境变量完全不用动。第二环境变量写在/etc/profile.d/java.sh要比直接改/etc/profile好得多——系统在登录时会自动加载/etc/profile.d/下所有的.sh文件你新增一个独立文件既清晰又不容易干扰系统原有配置卸载时删除这个文件就完事了。第三如果你是在自己的开发机上手动安装并且只想对当前用户生效也可以把同样的内容写进~/.bashrc但团队共用的服务器上建议放全局目录。2.3 两种方式怎么选一张表说清楚我自己的选型标准可以总结成一张表对比项包管理器安装手动解压安装安装路径系统统一管理如 /usr/lib/jvm/自定义目录如 /opt/java/版本选择跟随软件源通常保守任意版本完全掌控卸载干净程度高一条命令清理需要手动删目录、清理环境变量多版本共存自动集成 alternatives需要自己配软链接和脚本适合场景常规开发、部署求稳定特殊版本、特殊架构、需要精确控制如果你的目标是“最快让这台机器跑起Java项目”没什么好纠结的直接apt install或yum install。如果你是为了复现一个老项目需要JDK 8或者用着ARM架构的开发板那手动方案更合适。很多人喜欢“一步到位”全用手动装其实没必要包管理器带来的可维护性远超节省的那点下载时间。3. 环境变量配置为什么你照着敲还是失败如果说安装JDK本身难度是1那环境变量配置的难度至少是3。热搜词里“jdk环境变量配置失败”常年有大量搜索说明这是绝大多数人的痛点。配置环境变量看似就是往文件里加几行export但失败原因五花八门大多是概念没弄清楚。3.1 先搞懂 JAVA_HOME 和 PATH 各自干什么很多人配置环境变量时对这两行代码的含义说不清楚export JAVA_HOME/opt/java/current export PATH$JAVA_HOME/bin:$PATHJAVA_HOME是给那些“知道JDK在哪才能工作”的程序用的比如Tomcat、Maven、Gradle以及IDE它们需要JAVA_HOME来定位编译器和JVMPATH是给shell用的让你能直接在终端里敲java、javac而不是每次都要敲全路径/opt/java/current/bin/java。所以配置环境变量不是“抄两行就行”问题的关键在于JAVA_HOME一定要指向JDK的根目录而不是bin目录。我见过有人写JAVA_HOME/opt/java/current/bin结果Maven启动时直接报找不到JRE就是因为路径指错了一层。还有一个容易忽略的点PATH$JAVA_HOME/bin:$PATH这种写法是把JDK的bin目录追加到现有PATH的最前面。为什么要放前面因为Linux执行命令时按PATH顺序逐个查找放前面才能保证你敲java时优先命中自己配的JDK而不是系统自带的旧版本。总有人图省事写成export PATH/opt/java/current/bin直接把原有PATH覆盖掉连ls、vim都找不到了只能靠绝对路径救回来。3.2 配置写哪个文件登录Shell和交互Shell的差别环境变量配置失败最常见的原因不是语法错误而是文件写对了Shell加载顺序不对。Linux下的Shell配置文件执行顺序比较复杂但你需要记住两条核心规则登录Shell比如通过SSH登录服务器会读取/etc/profile以及/etc/profile.d/下的脚本个人用户还会读~/.profile。交互式非登录Shell比如在桌面环境里打开终端主要读~/.bashrc不读/etc/profile。这就解释了一个非常经典的“鬼现象”你在Ubuntu桌面上手动改了/etc/profile并source /etc/profile当前终端确实好了可是关掉重新开一个终端又变成原来的样子又或者你在服务器上配好了环境变量SSH登录没问题但脚本里执行java -version还是报 command not found。我在新服务器上配全局环境的习惯是始终使用/etc/profile.d/java.sh然后通过source /etc/profile.d/java.sh使当前会话生效。新开的终端和SSH会话都会自动加载不管是不是登录Shell覆盖面最大。如果你只配当前用户那么通常写~/.bashrc然后source ~/.bashrc。注意不要同时把内容写在/etc/profile和~/.bashrc里两个地方变量重复定义以后排查时容易“不知道哪个生效”。3.3 一个完整的排查链路照这个顺序查十分钟内定位如果你的java -version还不正常别急着重装按下面的顺序从外到内排查。这个排查链路我用的次数非常多基本能覆盖90%的配置失败# 1. 先看当前会话到底用的是哪个java找不到就是PATH没配好 which java # 如果结果是 /usr/bin/java说明走的是系统包默认的软链接 # 2. 看JAVA_HOME有没有值 echo $JAVA_HOME # 如果为空说明export那行没生效 # 3. 看PATH里有没有你的JDK目录 echo $PATH # 如果能看到 /opt/java/current/bin说明PATH配置到位 # 4. 追踪java真正指向的路径软链接背后的真实文件 readlink -f $(which java) # 如果是别的目录下的jdk说明alternatives或软链接优先级不对 # 5. 确认你改的文件本身没问题 cat /etc/profile.d/java.sh这一套下来问题基本就能定位到三类JAVA_HOME为空说明环境变量文件没有被加载which java找到的是系统的旧JDK说明PATH优先级没放到最前面readlink看到的实际路径和预期不符说明update-alternatives接管了java命令需要去调整alternatives优先级。记住配置环境变量的本质是让系统在正确的文件里拿到正确的内容并保证这些内容在目标Shell会话生效搞清这一点就不会被各种教程绕晕。3.4 一个被忽略的细节非交互Shell不读profile.d上面说的排查链路能解决大多数问题但有一种情况很隐蔽你的程序是通过Systemd服务或者cron定时任务启动的这些场景属于非交互Shell不读取用户目录的~/.bashrc某些配置也未必能加载/etc/profile.d/里的内容。结果就是你手动在终端跑java -version正常但服务一启动就报“找不到Java”。遇到这种情况不要试图让systemd去“读取环境变量文件”而是直接在服务启动脚本里写明JAVA_HOME和PATH或者用JVM的绝对路径。比如在systemd service文件里[Service] EnvironmentJAVA_HOME/opt/java/current EnvironmentPATH/opt/java/current/bin:/usr/bin:/bin ExecStart/opt/java/current/bin/java -jar /opt/app.jar这类问题排查起来特别费时间因为它不是“环境变量配错”而是“环境变量作用范围没覆盖到目标进程”。记下这个场景下次遇到服务起不来先怀疑进程的启动方式而不是怀疑JDK装坏了。4. 多JDK版本共存别再一台机器反复重装了打开热搜词就知道“java环境变量使用多个jdk”“jdk降级到17”这类需求有多高频。一台Linux机器上同时装多个JDK版本非常正常系统里可能有一个老项目需要JDK 8另一个新项目用JDK 17本地开发工具又要21。很多新手的做法是“装一个不行就卸载换另一个”折腾半个小时后发现还有别的项目要原来的版本心态直接崩了。其实Linux天生就支持多版本JDK共存只是切换方式需要分两种场景。4.1 包管理器场景用 update-alternatives 切换Debian系和RedHat系都有alternatives机制它的作用是把同一个命令比如java做成一个“候选列表”系统通过软链接指向当前选中的版本。如果你通过包管理器先后装了两个OpenJDK版本执行sudo update-alternatives --config java系统会列出所有已注册的JDK输入数字就能切换当前生效的java命令。同理javac也需要单独切换sudo update-alternatives --config javac这里有个非常容易被忽略的点update-alternatives只切换/usr/bin/java和/usr/bin/javac这些命令的指向它不会自动修改JAVA_HOME环境变量。也就是说你终端里java -version已经变成17了但echo $JAVA_HOME可能还指在8或者为空。Maven、IDEA这类工具通通认JAVA_HOME所以切换命令之后还要把/etc/profile.d/java.sh里的JAVA_HOME改成新版本对应的路径才能让整个系统“同步”。4.2 手动安装场景用软链接和切换脚本实现一键切换手动解压安装多个JDK时我的做法是所有JDK都放在/opt/java/jdk-版本号目录下然后按需切换current软链接。最朴素的方式是这样的# 切到JDK 8 sudo ln -sfn /opt/java/jdk-8 /opt/java/current # 切到JDK 17 sudo ln -sfn /opt/java/jdk-17 /opt/java/current由于环境变量里写的是JAVA_HOME/opt/java/current切换软链接后新开的终端会自动使用新JDK。但要注意当前已经打开的终端不会自动重新读取JAVA_HOME需要执行source /etc/profile.d/java.sh或者重开终端。如果你想连终端都不用关可以在~/.bashrc里写几个切换函数usejdk8() { export JAVA_HOME/opt/java/jdk-8 export PATH$JAVA_HOME/bin:$PATH java -version } usejdk17() { export JAVA_HOME/opt/java/jdk-17 export PATH$JAVA_HOME/bin:$PATH java -version }这样每次在终端输入usejdk17就能立刻把当前Shell切到JDK 17对开发机上的日常切换来说非常够用。这种脚本很浅但胜在直观团队里每个人拿到手就能用不用每个人重新解释一遍环境变量怎么配。4.3 更稳妥的方案手动安装的JDK也纳入 alternatives 管理如果希望手动安装的JDK也能用update-alternatives统一管理可以自己手动注册。注册命令sudo update-alternatives --install /usr/bin/java java /opt/java/jdk-17/bin/java 1700 sudo update-alternatives --install /usr/bin/javac javac /opt/java/jdk-17/bin/javac 1700末尾的1700是优先级数字数字大代表默认优先级高。注册完成后再执行sudo update-alternatives --config java你就能看到手动安装的JDK也出现在列表里。这种方式比单纯改软链接更规范java、javac命令和JAVA_HOME可以分别管理适合服务器上需要频繁切换版本又不想反复写脚本的场景。但这里还藏着一个最深的坑JAVA_HOME不会因为你update-alternatives切换而变化。alternatives管理的是命令软链接JAVA_HOME只是环境变量两个体系互不相干。我在帮人排查时经常遇到java -version输出17但Spring Boot启动日志里显示用的是8原因就是用户只切了alternatives/etc/profile.d/java.sh里的JAVA_HOME还一直指向8。这个必须成为条件反射切换JDK之后顺手检查一下JAVA_HOME。4.4 给IDE和中间件指定JDK版本的常见入口系统层面的切换搞定了还有一类需求发生在应用层。“idea配置jdk”“dbeaver 修改jdk版本”“jmeter 安装 jdk 8”这些热搜词的背后其实是同一个问题应用程序需要指定自己的Java运行时。IDE里很容易处理IntelliJ IDEA的Project Structure里可以添加多个JDK路径给每个项目单独指定SDKDBeaver在连接配置里或数据库驱动编辑器里可以单独选择驱动的Java Execution Environment。这类UI操作跟着菜单走就行重点提醒一点IDE里添加JDK时一定要指定到JDK根目录不是bin目录也不是jre目录否则极易出现“JDK加载失败”或“无效的SDK路径”。JMeter这类工具则通常在启动脚本里找JAVA_HOME可以修改jmeter脚本顶部的环境变量或者在setenv.sh里设置JAVA_HOME指向需要的版本。5. 卸载、验证与日常排错把这些经验存下来环境配好只是开始真正考验人的是后续维护。尤其当系统里存在多个JDK、多个工具链时卸载和排错反而成了日常高频操作。这里我把常见的卸载方式、报错对照和验证习惯一起整理出来希望能帮你少走一些弯路。5.1 卸载JDK包管理器一条命令手动安装要清理三处卸载方式取决于当初怎么装的。包管理器安装的Debian系用sudo apt purge openjdk-17-jdk-headless sudo apt autoremovepurge会连配置文件一起删autoremove会清理依赖安装但不再需要的包。RedHat系用sudo yum remove java-17-openjdk-devel如果是手动解压安装的卸载需要清理三处安装目录/opt/java/jdk-17、环境变量文件/etc/profile.d/java.sh以及如果注册过alternatives还要执行移除命令sudo update-alternatives --remove java /opt/java/jdk-17/bin/java sudo update-alternatives --remove javac /opt/java/jdk-17/bin/javac手动卸载最常见的坑是目录删了环境变量忘了删。结果新终端一打开就提示JAVA_HOME指向的路径不存在连终端提示符都变得奇怪或者反过来只删环境变量不删目录/opt/java里堆了一堆没用的包。我每次卸载手动安装的JDK都会写一个检查清单删完再开一个新终端验证一遍java -version和echo $JAVA_HOME肉眼确认干净了才算完成。5.2 高频报错对照表先知道“病根”再动手遇到报错不要急着百度整个句子先看报错的前几行关键词。这里整理几个我见过最多的高频报错报错关键词真正原因快速处理command not foundjavaPATH没生效或没配重看3.3排查链路JAVA_HOME is not defined correctlyJAVA_HOME指向了错误目录确认指向JDK根目录不要指向binUnsupportedClassVersionErrorclass文件编译版本高于运行JDK版本切换更高版本JDK或降低编译版本Unrecognized VM option老版本JVM参数用在了新JDK上检查启动脚本参数按新版本写法替换Error: Could not find or load main class类路径或类名不对检查CLASSPATH和启动类名排除拼写Cannot reserve enough space for object heap内存参数超过物理机可分配范围调小-Xmx或增大物理内存这张表里的每一条我都踩过。尤其是UnsupportedClassVersionError经常出现在“开发环境17测试环境8”的部署场景中解决方案只有一个让两边版本对齐。先用javac -version和java -version确认一致再谈其他。5.3 验证环境是否就绪一套固定动作建立肌肉记忆环境配置完成后我建议每次都用同一套命令验证不要只看java -version# 1. 看命令版本 java -version javac -version # 2. 看JAVA_HOME echo $JAVA_HOME # 3. 看实际路径 which java readlink -f $(which java) # 4. 看编译运行是否真的通畅关键一步 echo public class Test { public static void main(String[] args) { System.out.println(ok); } } /tmp/Test.java javac /tmp/Test.java java -cp /tmp Test为什么必须写一个最简单的类来编译运行因为java -version只证明JVM能启动不能证明javac可用、文件权限和目录可写都正常。我遇到过一台机器java -version一切正常javac也能输出版本但一编译就报“Permission denied”一查是/tmp目录挂载选项带了noexec。这种问题不写个Demo类根本测不出来。把这四条命令固化成肌肉记忆以后换任何一台机器配JDK两分钟就能确认环境真的可用。最后分享一个这几年带新人时的习惯把JDK安装和版本切换的步骤写进项目仓库的README并附上一份切换脚本。不要指望每个人都有同样的Linux基础也不要指望大家看一遍帖子和“内涵段子”就懂原理。环境这件事一个人配置容易团队统一配置靠流程。脚本可以放在scripts/jdk-env.sh里新同事拉下仓库执行source scripts/jdk-env.sh就能得到和团队一致的环境。毕竟项目的可复现性往往就是从环境配置开始建立的。
返回列表