
1. 从一台刚装好的 CentOS 7 虚拟机说起为什么大家总在 Java 版本上卡住在 VMware 里装完 CentOS 7第一件事往往是配网络、改 yum 源紧接着第二件事就是装 Java。这事看起来简单到不值一提但实际动手的人大概率都遇到过下面几种情况敲java -version出来的是 1.8.0_xxx可项目要求 OpenJDK 11javac能跑但JAVA_HOME指向的是 JRE 目录Maven 一编译就报找不到编译器或者更微妙的java和javac的版本居然不一致。这些问题的根源不在 Java 本身而在于 CentOS 7 这个系统里同时存在好几套 Java 装法它们各自维护自己的软链接和环境变量互相覆盖起来毫无提示。OpenJDK 11 是 LTS 版本从 2018 年发布到现在大量中间件、构建工具和微服务框架都以它为基准线。CentOS 7 默认自带的是 OpenJDK 8java-1.8.0-openjdk而且很多基础镜像或者最小化安装的虚拟机干脆一个 Java 都没有。所以给 CentOS 7 装 OpenJDK 11本质上是在一个停留时间较长、软件源偏保守的系统上把一个相对较新的运行时环境稳稳当当地落进去。装法其实就两条路走 yum 从软件仓库装或者下 tar.gz 二进制包手动部署。这两条路没有绝对的高下之分只有场景适配度不同。我在自己的测试虚拟机和生产服务器上都跑过踩过的坑分布得也挺均匀。下面把前置检查、两种装法的完整流程、装完之后的收尾动作以及几类典型故障的排查链路完整地摊开讲一遍。2. 动手前的三项前置检查别让环境把你坑了2.1 确认系统版本、架构和已有的 Java 残留CentOS 7 的小版本号直接决定了 yum 里有没有 OpenJDK 11 的包。7.7 及之后的版本官方的 updates 仓库里已经带了java-11-openjdk如果你手里是 7.2、7.4 这种早期版本yum search大概率什么都搜不到。所以第一件事是看版本cat /etc/redhat-release # 输出示例CentOS Linux release 7.9.2009 (Core) uname -m # x86_64 或者 aarch64这个决定后面下载哪个架构的包接着查系统里现在有哪些 Javarpm -qa | grep -i jdk rpm -qa | grep -i java which java javac java -version 21 echo $JAVA_HOME这几条命令看起来啰嗦但信息密度很高。rpm -qa能查出通过 yum 装过的包which能看出java这个命令当前解析到哪个路径正常情况应该指向/usr/bin/java而/usr/bin/java是个软链接最终会指向/etc/alternatives/java再指向真正的 JDK 目录。这里有个很多人忽略的细节即使rpm -qa什么都没查到which java也可能有输出。原因是有些安装脚本尤其是第三方一键部署包会自己下载 JDK 解压到/usr/local或者/opt然后往/etc/profile里塞一段export PATH。这种“野路子”装的 Java 不会出现在 rpm 数据库里但会导致你后面新装的版本被 PATH 顺序压住表现就是“明明装好了java -version还是旧的”。提示如果发现/etc/profile或/etc/bashrc里已经有手写的JAVA_HOME和PATH设置先记下位置别急着删等新版本装完再统一处理避免中途某个依赖 Java 的服务起不来。2.2 网络连通性与 yum 源可用性验证走 yum 安装的前提是软件源可达。VMware 里的虚拟机常见两种网络模式NAT 和桥接。NAT 模式下虚拟机通过宿主机的网络出口访问外网一般开箱即用如果用的是仅主机模式Host-Only那就完全没有外网这种情况下你只能走 tar.gz 离线包的路子。验证顺序建议是这样ping -c 3 223.5.5.5 # 测基础连通性 ping -c 3 mirrors.aliyun.com # 测 DNS 解析 外网 yum repolist # 看仓库列表是否正常拉取yum repolist这一条最关键它不只是测网络还顺带验证了仓库配置文件的语法。如果报 “Cannot find a valid baseurl for repo”通常不是网络问题而是 CentOS 7 早已停止常规维护默认的mirror.centos.org地址在某些环境下访问不稳定需要把源换成国内镜像。换源的操作很标准核心就是备份原文件、下载新的.repo文件、清理缓存mkdir -p /etc/yum.repos.d/bak mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/bak/ # 然后把国内镜像站提供的 CentOS 7 repo 文件放进 /etc/yum.repos.d/ yum clean all yum makecache2.3 磁盘空间与内存的最低要求OpenJDK 11 本身占的空间不大解压后约 300MB 左右加上-devel包和文档预留 1GB 绰绰有余。但 JVM 跑起来之后的内存开销才是重点。在 VMware 里给虚拟机分配内存时如果打算同时跑 Java 应用加数据库2GB 是起步线4GB 会比较从容。这个不是安装门槛但会影响你后面做压测或者跑构建时的体验。另外提醒一点/usr/lib/jvm这个目录是 CentOS 约定俗成的 Java 安装位置/tmp和/var也都在根分区下。如果你在装系统时把根分区划得很小比如 10GB装完几个 JDK 加构建工具之后可能会紧装之前用df -h扫一眼心里有数。3. 第一种方式用 yum 从软件仓库安装 OpenJDK 113.1 先搜索包名别凭记忆敲 installjava-11-openjdk这个包名听起来理所当然但仓库里实际拆成了好几个子包功能边界不一样装错了会发现少东西。先搜yum list available | grep -i openjdk yum search java-11搜出来的结果里你至少要认识下面这几个包名作用是否需要java-11-openjdk完整 JRE含图形相关组件一般需要java-11-openjdk-headless纯命令行 JRE无图形依赖服务器必装java-11-openjdk-devel开发工具链含javac、jar、jcmd等编译场景必装java-11-openjdk-jmods用于生成自定义运行时镜像特殊需求判断标准很直接只要服务器上需要编译 Java 代码或者要跑 Maven、Gradle 这类构建工具-devel就必须装如果只是跑一个已打包好的jar那-headless就够了。很多人图省事直接yum install java-11-openjdk结果发现javac不存在回头再补装-devel多花一遍时间。3.2 执行安装并观察依赖解析过程确定包名之后直接装yum install -y java-11-openjdk java-11-openjdk-devel这一步值得盯着输出看几秒重点看两件事。第一确认下载的版本号比如11.0.20.0.8-1.el7_9后面的el7_9说明这是针对 CentOS 7.9 打的包兼容性有保证。第二看依赖列表里有没有java-1.8.0-openjdk被一并拉进来。正常情况下不会OpenJDK 11 和 8 是并行安装的不会互相替换。如果看到 8 被引入说明你的仓库里有些包对 8 有强依赖这时候不要慌两个版本共存是完全允许的后面用 alternatives 切换就行。安装路径的落实结果通常在/usr/lib/jvm/java-11-openjdk-11.0.20.0.8-1.el7_9.x86_64/ # JDK 根目录 /usr/lib/jvm/jre-11-openjdk-11.0.20.0.8-1.el7_9.x86_64/ # JRE 目录注意JAVA_HOME必须指向上面那个带java-11-openjdk的目录而不是带jre-前缀的。这是个高频错误后面第 5 章会展开。3.3 验证安装结果与目录结构装完先不要急着配环境变量先用绝对路径直接跑一次/usr/lib/jvm/java-11-openjdk-11.0.20.0.8-1.el7_9.x86_64/bin/java -version如果输出类似openjdk version 11.0.20 2023-08-15 LTS OpenJDK Runtime Environment (Red_Hat-11.0.20.0.8-1.el7_9) (build 11.0.208-LTS) OpenJDK 64-Bit Server VM (Red_Hat-11.0.20.0.8-1.el7_9) (build 11.0.208-LTS, mixed mode, sharing)说明包本身没问题。这一步的意义在于把“包安装成功”和“命令可用”这两件事拆开验证。如果绝对路径能跑而java -version不行那问题 100% 出在软链接或 PATH 上排查方向立刻收窄。再顺手确认几个关键可执行文件都在ls -l /usr/lib/jvm/java-11-openjdk-*/bin/ | grep -E javac|jar|jps|jcmd|jmapjps、jcmd、jmap这几个诊断工具在排查线上问题时价值极高它们都属于-devel包如果这里缺了回头补装即可。提示yum 装的 OpenJDK 默认会被自动注册到 alternatives 系统里优先级一般设为 1100 左右。如果你后面装了别的版本切换时要注意优先级数字数字越大越优先。4. 第二种方式用 tar.gz 二进制包手动部署4.1 什么场景下应该放弃 yum 走手动部署yum 装法最大的限制是版本被仓库锁死你只能装仓库里有的那个小版本。如果有明确的版本要求比如某个中间件只认 11.0.17而仓库里只有 11.0.20那就只能手动部署。另外两种典型场景一是系统小版本太老7.4 以下仓库里压根没有 11二是要装到特定路径、或者要在一台机器上塞好几个不同小版本的 JDK 做并行测试。tar.gz 包的来源建议选正规构建渠道比如 Adoptium原 AdoptOpenJDK的发布页或者 OpenJDK 官方的归档页面。选择时要看清楚三个维度版本号、架构x64 / aarch64、以及是 JDK 还是 JRE。下载下来文件名通常长这样OpenJDK11U-jdk_x64_linux_hotspot_11.0.20_8.tar.gz4.2 解压、规划目录与权限设置约定俗成的做法是放到/usr/local下自己建一个语义清晰的目录名mkdir -p /usr/local/java cd /usr/local/java tar -zxvf /path/to/OpenJDK11U-jdk_x64_linux_hotspot_11.0.20_8.tar.gz # 解压后目录名通常很长改短便于管理 mv jdk-11.0.208 jdk-11 ls -ld /usr/local/java/jdk-11关于目录名我个人的习惯是保留小版本号比如jdk-11.0.20而不是笼统的jdk-11。原因是当你在同一台机器上升级到 11.0.21 时jdk-11这个名字就失去了区分度容易搞混。当然如果你只装一个版本用jdk-11也没问题后面环境变量写起来更省事。两种命名都行关键是全机器统一。权限方面如果只是自己用root拥有即可如果有多用户需要执行确保bin目录下的文件有x权限chmod -R 755 /usr/local/java/jdk-11用tar包的好处是它不带任何系统集成不碰 rpm 数据库也不动 alternatives完全靠环境变量生效。干净、可控代价是所有事情都要自己接。4.3 环境变量的写法三种位置三种生效范围环境变量写在哪里直接决定了它对谁生效、什么时候生效。这是手动部署里最容易出错的一环。第一种写/etc/profile.d/下新建一个独立脚本这是我推荐的方式cat /etc/profile.d/java11.sh EOF export JAVA_HOME/usr/local/java/jdk-11 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar EOF chmod x /etc/profile.d/java11.sh source /etc/profile.d/java11.sh这么写的好处是模块化以后要升级 JDK 或者卸载删掉这一个文件就干净了不会污染/etc/profile这个系统级文件。profile.d目录下的脚本会在登录 shell 启动时被/etc/profile自动读取执行顺序按文件名字母排序。第二种写/etc/profile末尾。能用但不推荐因为随着时间推移这个文件会被各种安装脚本追加内容最后变成一团乱麻排查 PATH 问题时非常痛苦。第三种写~/.bashrc或者~/.bash_profile。只对当前用户生效适合多用户环境下给某个人单独指定版本。注意~/.bashrc对非交互式 shell 的加载行为有细微差异如果 Java 是被 systemd 服务或者 cron 任务调用的这些场景通常不会读.bashrc写了也白写。这种情况下就得在服务单元文件里显式声明Environment或者在脚本里直接写绝对路径。PATH 的顺序也有讲究。$JAVA_HOME/bin:$PATH是把 JDK 目录放在最前面优先级最高这是对的。反过来写成$PATH:$JAVA_HOME/bin就会导致系统里原有的java命令先被找到你的新版本形同虚设。注意CLASSPATH这一行在现代 Java 项目里其实已经不需要了Maven、Gradle 都会自己管理依赖路径。加上它主要是为了兼容一些老脚本的写法写不写都不影响正常运行但写错了比如漏了当前目录的.反而可能出问题。新手建议先不加等确实遇到ClassNotFoundException再排查。5. 装完之后必须做的两件收尾工作5.1 alternatives 机制让 java 和 javac 同步切换CentOS 的 alternatives 系统是一套软链接管理机制。/usr/bin/java并不是真的 Java 程序而是一个指向/etc/alternatives/java的链接后者再指向实际位置。这样切换版本时只需要改中间那一层。手动部署的 JDK 不会自动注册到这套系统里需要自己加alternatives --install /usr/bin/java java /usr/local/java/jdk-11/bin/java 2000 alternatives --install /usr/bin/javac javac /usr/local/java/jdk-11/bin/javac 2000 alternatives --install /usr/bin/jar jar /usr/local/java/jdk-11/bin/jar 2000末尾的2000是优先级数值越大越优先。给它设成一个比较大的值是为了压过系统里可能存在的 OpenJDK 8默认优先级通常是 1081 左右。注册完之后切换alternatives --config java alternatives --config javac会列出所有已注册的候选项输入编号回车即可。这里有个非常隐蔽的坑alternatives --config java和alternatives --config javac是两个完全独立的配置项切了前者不会自动带上后者。如果你只切了java就会出现java -version显示 11 而javac -version显示 1.8 的诡异现象。Maven 编译时会直接报错退出。所以注册和切换时一定要成对操作把javac、jar、jarsigner这些都考虑到。5.2 JAVA_HOME 指向 JDK 而非 JRE 这个细节前面提过一次这里展开说清楚。一个完整的 JDK 目录结构是这样的/usr/local/java/jdk-11/ ├── bin/ # 包含 java、javac、jps 等 ├── conf/ ├── include/ ├── jmods/ ├── legal/ ├── lib/ └── release # 版本元信息而 JRE 目录下只有bin且只有java没有javac、lib、conf。在 OpenJDK 11 里JDK 和 JRE 的界限已经比 8 时代模糊了很多yum 包里你还能看到独立的jre-11-openjdk目录但如果JAVA_HOME指到了 JRE 目录会发生什么首先mvn compile会因为找不到编译器失败这是最直接的症状。其次某些依赖JAVA_HOME定位tools.jar或dt.jar的老工具会直接启动失败——虽然 JDK 9 之后这两个 jar 已经不存在了但依然有老脚本在检查它们。最麻烦的是有些问题不会立刻暴露等到某个特定功能被调用时才报错排查起来就要绕一大圈。验证方式很简单echo $JAVA_HOME ls $JAVA_HOME/bin/javac第二条命令如果报 “No such file or directory”说明JAVA_HOME指错了位置回去改。6. 几类真实故障的完整排查链路6.1 新装的版本不生效PATH 加载顺序排查现象java -version永远是 1.8无论怎么source都不变。排查要按顺序来不能跳步。第一步确认JAVA_HOME到底是什么echo $JAVA_HOME echo $PATH第二步看java命令实际解析到哪type -a java which -a java ls -l $(which java)type -a会列出所有匹配项按 PATH 顺序排列。如果第一个不是你要的版本说明 PATH 里有更靠前的目录。第三步查 PATH 是谁改的。这里用一个小技巧先重置成默认值再逐层加载env -i bash --login -c echo $PATH这会模拟一次干净的登录过程打印出最终的 PATH。然后对比一下就能判断是/etc/profile、/etc/profile.d/*.sh还是~/.bash_profile里塞进去的路径在捣乱。最常见的原因是/etc/profile.d/下有两个脚本一个设 Java 8 一个设 Java 11字母序在前面的那个把路径插到了最前面。解决办法是把 Java 11 的脚本重命名为字母序更靠前的名字比如00-java11.sh或者直接在旧脚本里删掉 Java 8 的 PATH 设置。6.2 多版本共存时的优先级混乱现象java -version是 11但javac -version是 1.8或者反过来。原因前面提过alternatives 是成对配置的。诊断命令alternatives --display java alternatives --display javac输出里会列出所有候选项、当前指向哪个、优先级是多少。看到不一致的情况直观得很。修复方式就是逐个alternatives --config统一切过去。另外补充一个很少人知道的操作可以用alternatives --set直接指定跳过交互式菜单alternatives --set java /usr/local/java/jdk-11/bin/java alternatives --set javac /usr/local/java/jdk-11/bin/javac这在写自动化脚本时特别有用因为不需要人肉输入编号。如果配置完还是不对检查一下/etc/alternatives/下的链接有没有断ls -l /etc/alternatives/java /etc/alternatives/javac链接指向的目标文件如果不存在说明你之前装的那个版本被删了但 alternatives 记录没清这时候得先用alternatives --remove清掉残留记录。6.3 服务启动失败但命令行正常现象SSH 进去敲java -version一切正常但 systemd 托管的那个 Java 服务启动就报找不到命令或者版本不对。这类问题的根源在于运行环境不同。交互式登录会读取/etc/profile和profile.d而 systemd 服务从/usr/lib/systemd/system/加载它的环境变量来自DefaultEnvironment和单元文件里的Environment指令两者完全不重叠。排查方式是直接看服务的实际环境systemctl show 服务名 -p Environment systemctl cat 服务名解决办法有两种。要么在单元文件的[Service]段落里显式加上EnvironmentJAVA_HOME/usr/local/java/jdk-11 EnvironmentPATH/usr/local/java/jdk-11/bin:/usr/bin:/bin要么在服务启动脚本里全部用绝对路径调用 Java最省事也最不容易出错。改完记得systemctl daemon-reload再重启服务。注意环境变量里的 PATH 一定要把/usr/bin和/bin带上只写 Java 目录会导致脚本里其他基础命令全部失效这种错误在容器环境里也经常出现。7. 关于选 yum 还是选 tar 包我自己的判断习惯开了不少台 CentOS 7 之后我基本形成了一套固定判断。凡是只需要一个稳定可用的 JDK、不挑小版本、机器能正常连软件源我一律走 yum。装完自动注册 alternativesjavac和java版本天然一致卸载用yum remove干净利落省心。而只要需求里出现了“必须 11.0.x”“要装三个不同小版本做兼容测试”“这台机器在纯内网没有可用的 yum 源”那就老老实实下 tar 包把版本号和路径都控制在自己手里。还有个容易忽略的点无论走哪条路装完之后都建议把java -version、javac -version、echo $JAVA_HOME这三条命令的输出记一笔写在部署文档或者机器备注里。半年之后你再回来看这台机器不用重新猜它到底装了什么翻一眼记录就清楚了。我在测试环境里吃过这个亏一台三年前配的机器/etc/profile.d/下躺着四个 Java 相关脚本最后靠grep -r JAVA_HOME /etc/才理清关系那次之后所有涉及多版本共存的机器我都留一份变更记录。