
1. 從一臺剛裝好的 CentOS 7 虛擬機說起為什么大家總在 Java 版本上卡住在 VMware 里裝完 CentOS 7第一件事往往是配網(wǎng)絡(luò)、改 yum 源緊接著第二件事就是裝 Java。這事看起來簡單到不值一提但實際動手的人大概率都遇到過下面幾種情況敲java -version出來的是 1.8.0_xxx可項目要求 OpenJDK 11javac能跑但JAVA_HOME指向的是 JRE 目錄Maven 一編譯就報找不到編譯器或者更微妙的java和javac的版本居然不一致。這些問題的根源不在 Java 本身而在于 CentOS 7 這個系統(tǒng)里同時存在好幾套 Java 裝法它們各自維護自己的軟鏈接和環(huán)境變量互相覆蓋起來毫無提示。OpenJDK 11 是 LTS 版本從 2018 年發(fā)布到現(xiàn)在大量中間件、構(gòu)建工具和微服務(wù)框架都以它為基準(zhǔn)線。CentOS 7 默認(rèn)自帶的是 OpenJDK 8java-1.8.0-openjdk而且很多基礎(chǔ)鏡像或者最小化安裝的虛擬機干脆一個 Java 都沒有。所以給 CentOS 7 裝 OpenJDK 11本質(zhì)上是在一個停留時間較長、軟件源偏保守的系統(tǒng)上把一個相對較新的運行時環(huán)境穩(wěn)穩(wěn)當(dāng)當(dāng)?shù)芈溥M去。裝法其實就兩條路走 yum 從軟件倉庫裝或者下 tar.gz 二進制包手動部署。這兩條路沒有絕對的高下之分只有場景適配度不同。我在自己的測試虛擬機和生產(chǎn)服務(wù)器上都跑過踩過的坑分布得也挺均勻。下面把前置檢查、兩種裝法的完整流程、裝完之后的收尾動作以及幾類典型故障的排查鏈路完整地攤開講一遍。2. 動手前的三項前置檢查別讓環(huán)境把你坑了2.1 確認(rèn)系統(tǒng)版本、架構(gòu)和已有的 Java 殘留CentOS 7 的小版本號直接決定了 yum 里有沒有 OpenJDK 11 的包。7.7 及之后的版本官方的 updates 倉庫里已經(jīng)帶了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這個決定后面下載哪個架構(gòu)的包接著查系統(tǒng)里現(xiàn)在有哪些 Javarpm -qa | grep -i jdk rpm -qa | grep -i java which java javac java -version 21 echo $JAVA_HOME這幾條命令看起來啰嗦但信息密度很高。rpm -qa能查出通過 yum 裝過的包which能看出java這個命令當(dāng)前解析到哪個路徑正常情況應(yīng)該指向/usr/bin/java而/usr/bin/java是個軟鏈接最終會指向/etc/alternatives/java再指向真正的 JDK 目錄。這里有個很多人忽略的細(xì)節(jié)即使rpm -qa什么都沒查到which java也可能有輸出。原因是有些安裝腳本尤其是第三方一鍵部署包會自己下載 JDK 解壓到/usr/local或者/opt然后往/etc/profile里塞一段export PATH。這種“野路子”裝的 Java 不會出現(xiàn)在 rpm 數(shù)據(jù)庫里但會導(dǎo)致你后面新裝的版本被 PATH 順序壓住表現(xiàn)就是“明明裝好了java -version還是舊的”。提示如果發(fā)現(xiàn)/etc/profile或/etc/bashrc里已經(jīng)有手寫的JAVA_HOME和PATH設(shè)置先記下位置別急著刪等新版本裝完再統(tǒng)一處理避免中途某個依賴 Java 的服務(wù)起不來。2.2 網(wǎng)絡(luò)連通性與 yum 源可用性驗證走 yum 安裝的前提是軟件源可達。VMware 里的虛擬機常見兩種網(wǎng)絡(luò)模式NAT 和橋接。NAT 模式下虛擬機通過宿主機的網(wǎng)絡(luò)出口訪問外網(wǎng)一般開箱即用如果用的是僅主機模式Host-Only那就完全沒有外網(wǎng)這種情況下你只能走 tar.gz 離線包的路子。驗證順序建議是這樣ping -c 3 223.5.5.5 # 測基礎(chǔ)連通性 ping -c 3 mirrors.aliyun.com # 測 DNS 解析 外網(wǎng) yum repolist # 看倉庫列表是否正常拉取yum repolist這一條最關(guān)鍵它不只是測網(wǎng)絡(luò)還順帶驗證了倉庫配置文件的語法。如果報 “Cannot find a valid baseurl for repo”通常不是網(wǎng)絡(luò)問題而是 CentOS 7 早已停止常規(guī)維護默認(rèn)的mirror.centos.org地址在某些環(huán)境下訪問不穩(wěn)定需要把源換成國內(nèi)鏡像。換源的操作很標(biāo)準(zhǔn)核心就是備份原文件、下載新的.repo文件、清理緩存mkdir -p /etc/yum.repos.d/bak mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/bak/ # 然后把國內(nèi)鏡像站提供的 CentOS 7 repo 文件放進 /etc/yum.repos.d/ yum clean all yum makecache2.3 磁盤空間與內(nèi)存的最低要求OpenJDK 11 本身占的空間不大解壓后約 300MB 左右加上-devel包和文檔預(yù)留 1GB 綽綽有余。但 JVM 跑起來之后的內(nèi)存開銷才是重點。在 VMware 里給虛擬機分配內(nèi)存時如果打算同時跑 Java 應(yīng)用加數(shù)據(jù)庫2GB 是起步線4GB 會比較從容。這個不是安裝門檻但會影響你后面做壓測或者跑構(gòu)建時的體驗。另外提醒一點/usr/lib/jvm這個目錄是 CentOS 約定俗成的 Java 安裝位置/tmp和/var也都在根分區(qū)下。如果你在裝系統(tǒng)時把根分區(qū)劃得很小比如 10GB裝完幾個 JDK 加構(gòu)建工具之后可能會緊裝之前用df -h掃一眼心里有數(shù)。3. 第一種方式用 yum 從軟件倉庫安裝 OpenJDK 113.1 先搜索包名別憑記憶敲 installjava-11-openjdk這個包名聽起來理所當(dāng)然但倉庫里實際拆成了好幾個子包功能邊界不一樣裝錯了會發(fā)現(xiàn)少東西。先搜yum list available | grep -i openjdk yum search java-11搜出來的結(jié)果里你至少要認(rèn)識下面這幾個包名作用是否需要java-11-openjdk完整 JRE含圖形相關(guān)組件一般需要java-11-openjdk-headless純命令行 JRE無圖形依賴服務(wù)器必裝java-11-openjdk-devel開發(fā)工具鏈含javac、jar、jcmd等編譯場景必裝java-11-openjdk-jmods用于生成自定義運行時鏡像特殊需求判斷標(biāo)準(zhǔn)很直接只要服務(wù)器上需要編譯 Java 代碼或者要跑 Maven、Gradle 這類構(gòu)建工具-devel就必須裝如果只是跑一個已打包好的jar那-headless就夠了。很多人圖省事直接yum install java-11-openjdk結(jié)果發(fā)現(xiàn)javac不存在回頭再補裝-devel多花一遍時間。3.2 執(zhí)行安裝并觀察依賴解析過程確定包名之后直接裝yum install -y java-11-openjdk java-11-openjdk-devel這一步值得盯著輸出看幾秒重點看兩件事。第一確認(rèn)下載的版本號比如11.0.20.0.8-1.el7_9后面的el7_9說明這是針對 CentOS 7.9 打的包兼容性有保證。第二看依賴列表里有沒有java-1.8.0-openjdk被一并拉進來。正常情況下不會OpenJDK 11 和 8 是并行安裝的不會互相替換。如果看到 8 被引入說明你的倉庫里有些包對 8 有強依賴這時候不要慌兩個版本共存是完全允許的后面用 alternatives 切換就行。安裝路徑的落實結(jié)果通常在/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 驗證安裝結(jié)果與目錄結(jié)構(gòu)裝完先不要急著配環(huán)境變量先用絕對路徑直接跑一次/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 上排查方向立刻收窄。再順手確認(rèn)幾個關(guān)鍵可執(zhí)行文件都在ls -l /usr/lib/jvm/java-11-openjdk-*/bin/ | grep -E javac|jar|jps|jcmd|jmapjps、jcmd、jmap這幾個診斷工具在排查線上問題時價值極高它們都屬于-devel包如果這里缺了回頭補裝即可。提示yum 裝的 OpenJDK 默認(rèn)會被自動注冊到 alternatives 系統(tǒng)里優(yōu)先級一般設(shè)為 1100 左右。如果你后面裝了別的版本切換時要注意優(yōu)先級數(shù)字?jǐn)?shù)字越大越優(yōu)先。4. 第二種方式用 tar.gz 二進制包手動部署4.1 什么場景下應(yīng)該放棄 yum 走手動部署yum 裝法最大的限制是版本被倉庫鎖死你只能裝倉庫里有的那個小版本。如果有明確的版本要求比如某個中間件只認(rèn) 11.0.17而倉庫里只有 11.0.20那就只能手動部署。另外兩種典型場景一是系統(tǒng)小版本太老7.4 以下倉庫里壓根沒有 11二是要裝到特定路徑、或者要在一臺機器上塞好幾個不同小版本的 JDK 做并行測試。tar.gz 包的來源建議選正規(guī)構(gòu)建渠道比如 Adoptium原 AdoptOpenJDK的發(fā)布頁或者 OpenJDK 官方的歸檔頁面。選擇時要看清楚三個維度版本號、架構(gòu)x64 / aarch64、以及是 JDK 還是 JRE。下載下來文件名通常長這樣OpenJDK11U-jdk_x64_linux_hotspot_11.0.20_8.tar.gz4.2 解壓、規(guī)劃目錄與權(quán)限設(shè)置約定俗成的做法是放到/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關(guān)于目錄名我個人的習(xí)慣是保留小版本號比如jdk-11.0.20而不是籠統(tǒng)的jdk-11。原因是當(dāng)你在同一臺機器上升級到 11.0.21 時jdk-11這個名字就失去了區(qū)分度容易搞混。當(dāng)然如果你只裝一個版本用jdk-11也沒問題后面環(huán)境變量寫起來更省事。兩種命名都行關(guān)鍵是全機器統(tǒng)一。權(quán)限方面如果只是自己用root擁有即可如果有多用戶需要執(zhí)行確保bin目錄下的文件有x權(quán)限chmod -R 755 /usr/local/java/jdk-11用tar包的好處是它不帶任何系統(tǒng)集成不碰 rpm 數(shù)據(jù)庫也不動 alternatives完全靠環(huán)境變量生效。干凈、可控代價是所有事情都要自己接。4.3 環(huán)境變量的寫法三種位置三種生效范圍環(huán)境變量寫在哪里直接決定了它對誰生效、什么時候生效。這是手動部署里最容易出錯的一環(huán)。第一種寫/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這個系統(tǒng)級文件。profile.d目錄下的腳本會在登錄 shell 啟動時被/etc/profile自動讀取執(zhí)行順序按文件名字母排序。第二種寫/etc/profile末尾。能用但不推薦因為隨著時間推移這個文件會被各種安裝腳本追加內(nèi)容最后變成一團亂麻排查 PATH 問題時非常痛苦。第三種寫~/.bashrc或者~/.bash_profile。只對當(dāng)前用戶生效適合多用戶環(huán)境下給某個人單獨指定版本。注意~/.bashrc對非交互式 shell 的加載行為有細(xì)微差異如果 Java 是被 systemd 服務(wù)或者 cron 任務(wù)調(diào)用的這些場景通常不會讀.bashrc寫了也白寫。這種情況下就得在服務(wù)單元文件里顯式聲明Environment或者在腳本里直接寫絕對路徑。PATH 的順序也有講究。$JAVA_HOME/bin:$PATH是把 JDK 目錄放在最前面優(yōu)先級最高這是對的。反過來寫成$PATH:$JAVA_HOME/bin就會導(dǎo)致系統(tǒng)里原有的java命令先被找到你的新版本形同虛設(shè)。注意CLASSPATH這一行在現(xiàn)代 Java 項目里其實已經(jīng)不需要了Maven、Gradle 都會自己管理依賴路徑。加上它主要是為了兼容一些老腳本的寫法寫不寫都不影響正常運行但寫錯了比如漏了當(dāng)前目錄的.反而可能出問題。新手建議先不加等確實遇到ClassNotFoundException再排查。5. 裝完之后必須做的兩件收尾工作5.1 alternatives 機制讓 java 和 javac 同步切換CentOS 的 alternatives 系統(tǒng)是一套軟鏈接管理機制。/usr/bin/java并不是真的 Java 程序而是一個指向/etc/alternatives/java的鏈接后者再指向?qū)嶋H位置。這樣切換版本時只需要改中間那一層。手動部署的 JDK 不會自動注冊到這套系統(tǒng)里需要自己加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是優(yōu)先級數(shù)值越大越優(yōu)先。給它設(shè)成一個比較大的值是為了壓過系統(tǒng)里可能存在的 OpenJDK 8默認(rèn)優(yōu)先級通常是 1081 左右。注冊完之后切換alternatives --config java alternatives --config javac會列出所有已注冊的候選項輸入編號回車即可。這里有個非常隱蔽的坑alternatives --config java和alternatives --config javac是兩個完全獨立的配置項切了前者不會自動帶上后者。如果你只切了java就會出現(xiàn)java -version顯示 11 而javac -version顯示 1.8 的詭異現(xiàn)象。Maven 編譯時會直接報錯退出。所以注冊和切換時一定要成對操作把javac、jar、jarsigner這些都考慮到。5.2 JAVA_HOME 指向 JDK 而非 JRE 這個細(xì)節(jié)前面提過一次這里展開說清楚。一個完整的 JDK 目錄結(jié)構(gòu)是這樣的/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 的界限已經(jīng)比 8 時代模糊了很多yum 包里你還能看到獨立的jre-11-openjdk目錄但如果JAVA_HOME指到了 JRE 目錄會發(fā)生什么首先mvn compile會因為找不到編譯器失敗這是最直接的癥狀。其次某些依賴JAVA_HOME定位tools.jar或dt.jar的老工具會直接啟動失敗——雖然 JDK 9 之后這兩個 jar 已經(jīng)不存在了但依然有老腳本在檢查它們。最麻煩的是有些問題不會立刻暴露等到某個特定功能被調(diào)用時才報錯排查起來就要繞一大圈。驗證方式很簡單echo $JAVA_HOME ls $JAVA_HOME/bin/javac第二條命令如果報 “No such file or directory”說明JAVA_HOME指錯了位置回去改。6. 幾類真實故障的完整排查鏈路6.1 新裝的版本不生效PATH 加載順序排查現(xiàn)象java -version永遠(yuǎn)是 1.8無論怎么source都不變。排查要按順序來不能跳步。第一步確認(rèn)JAVA_HOME到底是什么echo $JAVA_HOME echo $PATH第二步看java命令實際解析到哪type -a java which -a java ls -l $(which java)type -a會列出所有匹配項按 PATH 順序排列。如果第一個不是你要的版本說明 PATH 里有更靠前的目錄。第三步查 PATH 是誰改的。這里用一個小技巧先重置成默認(rèn)值再逐層加載env -i bash --login -c echo $PATH這會模擬一次干凈的登錄過程打印出最終的 PATH。然后對比一下就能判斷是/etc/profile、/etc/profile.d/*.sh還是~/.bash_profile里塞進去的路徑在搗亂。最常見的原因是/etc/profile.d/下有兩個腳本一個設(shè) Java 8 一個設(shè) Java 11字母序在前面的那個把路徑插到了最前面。解決辦法是把 Java 11 的腳本重命名為字母序更靠前的名字比如00-java11.sh或者直接在舊腳本里刪掉 Java 8 的 PATH 設(shè)置。6.2 多版本共存時的優(yōu)先級混亂現(xiàn)象java -version是 11但javac -version是 1.8或者反過來。原因前面提過alternatives 是成對配置的。診斷命令alternatives --display java alternatives --display javac輸出里會列出所有候選項、當(dāng)前指向哪個、優(yōu)先級是多少??吹讲灰恢碌那闆r直觀得很。修復(fù)方式就是逐個alternatives --config統(tǒng)一切過去。另外補充一個很少人知道的操作可以用alternatives --set直接指定跳過交互式菜單alternatives --set java /usr/local/java/jdk-11/bin/java alternatives --set javac /usr/local/java/jdk-11/bin/javac這在寫自動化腳本時特別有用因為不需要人肉輸入編號。如果配置完還是不對檢查一下/etc/alternatives/下的鏈接有沒有斷l(xiāng)s -l /etc/alternatives/java /etc/alternatives/javac鏈接指向的目標(biāo)文件如果不存在說明你之前裝的那個版本被刪了但 alternatives 記錄沒清這時候得先用alternatives --remove清掉殘留記錄。6.3 服務(wù)啟動失敗但命令行正?,F(xiàn)象SSH 進去敲java -version一切正常但 systemd 托管的那個 Java 服務(wù)啟動就報找不到命令或者版本不對。這類問題的根源在于運行環(huán)境不同。交互式登錄會讀取/etc/profile和profile.d而 systemd 服務(wù)從/usr/lib/systemd/system/加載它的環(huán)境變量來自DefaultEnvironment和單元文件里的Environment指令兩者完全不重疊。排查方式是直接看服務(wù)的實際環(huán)境systemctl show 服務(wù)名 -p Environment systemctl cat 服務(wù)名解決辦法有兩種。要么在單元文件的[Service]段落里顯式加上EnvironmentJAVA_HOME/usr/local/java/jdk-11 EnvironmentPATH/usr/local/java/jdk-11/bin:/usr/bin:/bin要么在服務(wù)啟動腳本里全部用絕對路徑調(diào)用 Java最省事也最不容易出錯。改完記得systemctl daemon-reload再重啟服務(wù)。注意環(huán)境變量里的 PATH 一定要把/usr/bin和/bin帶上只寫 Java 目錄會導(dǎo)致腳本里其他基礎(chǔ)命令全部失效這種錯誤在容器環(huán)境里也經(jīng)常出現(xiàn)。7. 關(guān)于選 yum 還是選 tar 包我自己的判斷習(xí)慣開了不少臺 CentOS 7 之后我基本形成了一套固定判斷。凡是只需要一個穩(wěn)定可用的 JDK、不挑小版本、機器能正常連軟件源我一律走 yum。裝完自動注冊 alternativesjavac和java版本天然一致卸載用yum remove干凈利落省心。而只要需求里出現(xiàn)了“必須 11.0.x”“要裝三個不同小版本做兼容測試”“這臺機器在純內(nèi)網(wǎng)沒有可用的 yum 源”那就老老實實下 tar 包把版本號和路徑都控制在自己手里。還有個容易忽略的點無論走哪條路裝完之后都建議把java -version、javac -version、echo $JAVA_HOME這三條命令的輸出記一筆寫在部署文檔或者機器備注里。半年之后你再回來看這臺機器不用重新猜它到底裝了什么翻一眼記錄就清楚了。我在測試環(huán)境里吃過這個虧一臺三年前配的機器/etc/profile.d/下躺著四個 Java 相關(guān)腳本最后靠grep -r JAVA_HOME /etc/才理清關(guān)系那次之后所有涉及多版本共存的機器我都留一份變更記錄。