
搞Java開發(fā)的人誰電腦里沒裝過兩三個JDK版本老項目要Java 8新項目已經(jīng)升到Java 17甚至21偶爾還得試一下預(yù)覽版本的新特性。SDKMAN就是這樣一個命令行工具專門解決“裝JDK、換JDK、刪JDK”這一整套管理問題支持在同一個系統(tǒng)里并存多個JDK版本并且隨時切換。不管你是剛?cè)腴T的新手還是被多版本折騰過好幾次的開發(fā)者這篇文章能幫你把整套流程理順。我會直接告訴你怎么裝、怎么用、怎么切順便把我踩過的坑也一起列出來。1. 為什么你的電腦需要裝多個JDK版本1.1 日常開發(fā)里的版本分裂場景很多人的第一反應(yīng)是JDK裝一個最新的不就行了嗎真不行。實際項目里版本分裂的情況太常見了。比如有些老業(yè)務(wù)系統(tǒng)用的是Java 8那一套生態(tài)依賴庫里有些組件在JDK 8之后就不再維護了強行用新版本跑起來會出現(xiàn)兼容性問題。而新起的微服務(wù)項目一般會考慮Java 17或者Java 21帶來的新特性和性能改進。兩邊同事協(xié)作時你手里如果只有一個最新的JDK切換項目時就會頻繁碰到編譯不過、運行報錯、依賴包反射調(diào)用失敗之類的問題。還有一類場景平時不太容易想到你在本地跑一些中間件或者腳本工具時它們內(nèi)置的要求往往比項目本身更嚴格。有些工具只認某個特定版本的JVM版本高了或者低了啟動腳本的行為都不一樣。這種時候多版本共存幾乎是唯一省心的方案。1.2 手動管理JDK的痛點沒有工具之前我見過不少同事是這樣的從官網(wǎng)下載壓縮包解壓到某個目錄再打開系統(tǒng)環(huán)境變量設(shè)置界面手動加JAVA_HOME再把$JAVA_HOME/bin拼到PATH里。這流程聽起來不難真正煩的是切換。今天切到Java 8明天要切到Java 17你得重新打開環(huán)境變量配置把JAVA_HOME改成另一個路徑然后保存、重開終端運行java -version確認。改一次還行一天改好幾次就會特別煩躁而且萬一有幾個終端窗口沒重開內(nèi)存里的環(huán)境變量還是舊的還會產(chǎn)生“明明切換了但沒生效”的錯覺。手動解壓的方式還有一個隱藏問題JDK版本信息是分散的。你下載了好幾個版本的壓縮包時間一長你自己都分不清哪個目錄對應(yīng)哪個版本也不敢亂刪怕刪了某個正在跑的項目就崩了。系統(tǒng)的包管理器雖然能裝但版本更新通常滯后而且它會把JDK和軟件倉庫的其他組件混在一起很多時候并不適合精確控制項目的運行時。所以我之后換成專用的版本管理工具整個流程就清爽多了。2. 先弄懂SDKMAN是什么2.1 包管理器之外的另一個身份SDKMAN全稱是Software Development Kit Manager很多人把它當(dāng)成包管理器其實它的主要身份是“版本管理工具”。它不直接參與編譯你的代碼也不會把某個SDK“裝進系統(tǒng)里”讓你在系統(tǒng)設(shè)置中看到它而是負責(zé)在你的用戶目錄下維護一套“候選版本”列表并且通過符號鏈接的方式隨時切換當(dāng)前生效的版本。這套思路和Node生態(tài)里的nvm、Python生態(tài)里的pyenv非常相似。如果你熟悉其中任何一個工具理解SDKMAN會很快。它會把自己的目錄放在~/.sdkman/candidates/java/下面每個已安裝的JDK對應(yīng)一個子目錄比如17.0.12-tem、21.0.3-tem。同時會有一個current符號鏈接指向當(dāng)前正在使用的版本。你的JAVA_HOME只需要指向~/.sdkman/candidates/java/current切換版本時SDKMAN會修改這個鏈接的指向其他所有配置都不用動。2.2 為什么選它而不是去官網(wǎng)下載官網(wǎng)下載本身沒什么問題但那種方式只適合你一次性裝好、再也不換版本的情況。只要你有兩個以上版本的需求手工管理方式的效率低到可以忽略。SDKMAN相比其他管理方式有幾點很實用。第一它維護的候選版本列表非常全不僅僅是原始JDK發(fā)行版還包括各種OpenJDK發(fā)行版比如Temurin、Zulu、Liberica等。第二所有操作都在用戶目錄里完成不需要sudo權(quán)限也不會污染系統(tǒng)級別的環(huán)境變量。第三它統(tǒng)一了安裝、卸載、切換的入口命令風(fēng)格非常一致學(xué)一次就能用在Java之外的SDK上比如Maven、Gradle、Spring Boot CLI等也都支持管理。還有一點對我個人來說很省心它可以幫你記錄當(dāng)前安裝了哪些版本、每個版本對應(yīng)哪個項目。你不需要再維護一份文本清單來記錄誰是誰了。2.3 運行環(huán)境的前置條件SDKMAN本身是用Shell腳本寫的所以在Unix-like系統(tǒng)上運行最順暢常見的Linux發(fā)行版、macOS都支持。你需要的基本上是一個能跑bash和curl的終端環(huán)境Windows下可以通過WSL或者Git Bash之類的環(huán)境來用。安裝之前最好先確認一下curl命令可用方法是在終端里執(zhí)行curl --version如果提示找不到命令先補上curl再繼續(xù)。3. SDKMAN的安裝與基礎(chǔ)配置3.1 安裝腳本的完整執(zhí)行過程官方給的安裝命令是一行比較長的管道命令curl -s https://get.sdkman.io | bash執(zhí)行后腳本會在你的用戶目錄下創(chuàng)建一個.sdkman目錄把運行需要的腳本、配置、候選版本目錄結(jié)構(gòu)都準備好。同時它還會自動往你的shell配置文件中追加幾行目的是在每次開啟新的終端窗口時自動加載SDKMAN環(huán)境。如果你用的是bash它改的是~/.bashrc如果是zsh則改~/.zshrc。裝完之后立刻在當(dāng)前終端窗口內(nèi)執(zhí)行source $HOME/.sdkman/bin/sdkman-init.sh然后執(zhí)行sdk version如果能正常輸出版本號就說明安裝成功了。下次打開新的終端時就不需要再手動執(zhí)行這條初始化命令了因為它已經(jīng)被寫進了shell配置文件里。3.2 初始化與驗證我建議安裝完以后先確認三件事一是sdk version能正常打印版本號二是echo $SDKMAN_DIR能輸出路徑三是which sdk能找到可執(zhí)行命令。如果前兩步都正常但第三步提示找不到命令多半是當(dāng)前shell環(huán)境沒有重新加載配置導(dǎo)致的。對于zsh用戶如果sdk version在交互終端中能用但在腳本中調(diào)用失敗檢查一下.zshrc里加載SDKMAN的幾行代碼的位置是否太靠前。有個比較常見的問題是用戶把alias相關(guān)的代碼寫在了SDKMAN初始化之前導(dǎo)致某些場景下初始化沒有正常執(zhí)行。這時候把init相關(guān)代碼調(diào)整到文件靠前位置基本就能解決。3.3 常見安裝報錯排查安裝過程中最容易出的問題就是下載腳本超時。如果curl執(zhí)行到一半卡住或者報連接失敗的錯可以先檢查網(wǎng)絡(luò)環(huán)境是否正常然后嘗試把get.sdkman.io換成社區(qū)提供的鏡像源或者設(shè)置系統(tǒng)級的環(huán)境變量讓curl走可用的通道。這里不展開細節(jié)但有一點要重點提示注意官方安裝腳本本質(zhì)是從遠程下載并直接執(zhí)行因此在運行之前最好先把它下載到本地快速瀏覽一遍腳本內(nèi)容確認沒有問題后再執(zhí)行。謹慎一點不是壞事。如果之前裝過SDKMAN但中途失敗重裝前建議先把~/.sdkman目錄和shell配置文件里相關(guān)的一小段配置清理掉否則可能出現(xiàn)兩個版本的初始化代碼互相干擾的情況。4. 用SDKMAN安裝JDK版本4.1 先列出可選版本安裝之前先看看官方倉庫里目前有哪些版本可用執(zhí)行sdk list java輸出信息很豐富。每行會給出一個版本標識符identifier比如17.0.12-tem、21.0.3-tem后面會標明它屬于哪個發(fā)行版比如Temurin、Zulu、Liberica、Oracle等還會標出當(dāng)前安裝狀態(tài)、已安裝和本地版本的區(qū)別。剛接觸這個命令的人容易對著屏幕發(fā)懵其實需要注意的只有四列標識符、發(fā)行版、狀態(tài)、使用類型。在這之前還有必要了解發(fā)行版之間的差異。Temurin是Eclipse社區(qū)維護的OpenJDK發(fā)行版穩(wěn)定且免費適合大多數(shù)后端項目Zulu對多平臺的支持比較全移動端和一些嵌入式場景經(jīng)常用到Liberica在桌面開發(fā)和特殊架構(gòu)上的支持做得不錯Oracle發(fā)行版則更多面向需要商業(yè)支持的場景。不同發(fā)行版之間整體API幾乎一致只是補丁節(jié)奏和支持策略不同。多數(shù)情況下選Temurin就能順利跑完整個項目周期。4.2 安裝指定發(fā)行版選好版本后直接輸入sdk install java 17.0.12-tem這個時候SDKMAN會開始下載對應(yīng)的壓縮包自動解壓到~/.sdkman/candidates/java/17.0.12-tem目錄下。下載速度取決于網(wǎng)絡(luò)環(huán)境和所選發(fā)行版一般幾分鐘內(nèi)能完成。安裝完成后它還會在終端里問你是否把這個版本設(shè)為默認版本輸入Y表示確認輸入n表示僅安裝但不切換默認值。如果你需要好幾個版本就可以反復(fù)執(zhí)行sdk install java 8.0.432-tem sdk install java 21.0.3-tem裝完整理后的目錄大致是這樣~/.sdkman/candidates/java/ ├── 8.0.432-tem ├── 17.0.12-tem ├── 21.0.3-tem └── current - 17.0.12-tem4.3 安裝到指定路徑的原理說明如果你愿意也可以通過環(huán)境變量控制安裝目錄的頂層路徑。SDKMAN默認把一切放在~/.sdkman下但這個路徑不是寫死的靠的是SDKMAN_DIR環(huán)境變量。在安裝之前設(shè)置這個變量就能把整個SDKMAN的數(shù)據(jù)目錄挪到其他磁盤分區(qū)或者目錄。有些公司會有統(tǒng)一的開發(fā)目錄規(guī)范利用這個變量可以避免把個人數(shù)據(jù)放在默認目錄下。不過我不太建議普通用戶做這種自定義。因為很多工具和IDE默認會搜索~/.sdkman這個路徑改了以后你需要額外告訴每個工具SDKMAN的位置反而多了一步配置。5. 多版本切換的幾種姿勢5.1 全局切換和當(dāng)前窗口臨時切換default和use是兩個容易混淆的命令但區(qū)別很清晰。default是持久化操作會讓某個版本成為以后每次打開終端時的默認版本修改的是~/.sdkman/etc/config中的配置。use則只管當(dāng)前這個終端窗口窗口關(guān)閉后也就失效了。sdk default java 21.0.3-tem sdk use java 17.0.12-tem實際使用中我會按照項目性質(zhì)來決定用哪個。長期在維護的老項目就把默認版本設(shè)置成Java 8臨時要測試一個新框架就用use在當(dāng)前窗口切換到Java 17測試完重開一個終端就回到了默認版本。5.2 按目錄自動切換這個功能是SDKMAN里非常值得推薦的一個特性——.sdkmanrc。思路很簡單在每個項目目錄下放一個.sdkmanrc文件文件內(nèi)容指定這個項目要用哪個SDK版本。進入目錄后執(zhí)行sdk envSDKMAN會讀取當(dāng)前目錄和上級目錄中最近的.sdkmanrc文件并按照文件內(nèi)容自動切換版本。文件內(nèi)容很簡單比如java8.0.432-tem如果把這一行寫進某個老項目根目錄的.sdkmanrc文件之后進入這個目錄執(zhí)行sdk envJDK就會自動切到Java 8。同理在新項目目錄里再寫一行java21.0.3-tem切目錄時就能大幅減少手動切換的工作量。需要注意sdk env只作用于當(dāng)前終端會話。如果你開了多個終端窗口每個窗口都要單獨執(zhí)行一次不會自動跨窗口生效。5.3 設(shè)置默認版本默認版本的設(shè)置非常值得單獨說一下。我們經(jīng)常遇到“明明已經(jīng)切換了但刷新環(huán)境后版本又變了”的情況。這種時候一般在重構(gòu)某個老項目切回默認版本再切到目標版本時會發(fā)現(xiàn)java -version仍然顯示舊的。原因往往是IDE保留了之前的JAVA_HOME緩存或者終端會話是從舊環(huán)境繼承過來的。用default切換后重開終端是保證生效最實在的辦法。如果不想重開就重新執(zhí)行source $HOME/.sdkman/bin/sdkman-init.sh再驗證。記住JAVA_HOME看著是current路徑但進程里的環(huán)境變量是啟動那一刻快照出來的不是實時動態(tài)變化。6. 常用命令速查與腳本化技巧6.1 常用命令速查我整理了一份使用頻率最高的命令表方便復(fù)制到自己的筆記里。這里不包含全部功能只保留了日常能真正用到的部分。命令作用sdk list java查看所有可安裝的JDK版本sdk install java 17.0.12-tem安裝指定版本sdk uninstall java 17.0.12-tem卸載指定版本sdk use java 17.0.12-tem當(dāng)前窗口切換到指定版本sdk default java 17.0.12-tem全局默認切換到指定版本sdk current查看當(dāng)前所有SDK版本的狀態(tài)sdk upgrade java升級Java相關(guān)的SDK包sdk offline進入離線模式只使用本地已安裝版本sdk current這個命令經(jīng)常被低估。執(zhí)行以后能一次性看到Java、Maven、Gradle等所有由SDKMAN管理的候選版本當(dāng)前狀態(tài)比挨個java -version和mvn -version直觀得多。6.2 在CI或腳本環(huán)境中使用SDKMAN不只是給本地終端用的在服務(wù)器、構(gòu)建機上同樣能發(fā)揮作用。打包腳本或者持續(xù)集成流程里可以先通過source加載初始化腳本再安裝指定版本然后設(shè)置默認值一次性把環(huán)境準備到位。比如export SDKMAN_DIR$HOME/.sdkman source $HOME/.sdkman/bin/sdkman-init.sh sdk install java 17.0.12-tem sdk default java 17.0.12-tem這里有一個小技巧在腳本里反復(fù)執(zhí)行sdk install java時如果版本已經(jīng)安裝過了它會顯示“already installed”并直接跳過后續(xù)操作不會重復(fù)下載所以即使腳本被多次執(zhí)行代價也很低。如果需要完全清除后重裝可以先rm -rf ~/.sdkman/candidates/java/目標版本再執(zhí)行安裝命令。6.3 離線模式的使用場景我有時會遇到構(gòu)建機網(wǎng)絡(luò)受限的情況此時SDKMAN無法訪問遠程倉庫列表但本機上其實已經(jīng)裝好了需要的版本。執(zhí)行sdk offline進入離線模式后安裝、切換、卸載都只基于本地緩存不會嘗試連接遠程的版本列表。這個模式在隔離環(huán)境里特別有用。退出離線模式執(zhí)行sdk online即可操作很輕量。7. 踩坑記錄與避坑指南7.1 踩坑一版本列表拉不下來或超時表現(xiàn)是執(zhí)行sdk list java后長時間沒有輸出或者提示Could not resolve host之類的錯誤。常見原因除了網(wǎng)絡(luò)不通還有可能是CDN節(jié)點響應(yīng)過慢。解決辦法是從官方發(fā)布頁獲取鏡像源信息修改~/.sdkman/etc/config里的sdkman_webservice配置換成可用的鏡像地址然后執(zhí)行sdk update刷新列表。如果只是臨時需要本地已有版本直接進離線模式用就好。7.2 踩坑二IDE不識別SDKMAN路徑這個問題常在配置開發(fā)環(huán)境時出現(xiàn)。你在終端里java -version一切正常但IDE里項目SDK卻顯示無效路徑。原因是IDE啟動時是從桌面圖標或啟動器讀取環(huán)境的它不一定繼承了終端里source過的SDKMAN配置。解決辦法有三個一是從終端直接啟動IDE讓IDE繼承shell環(huán)境二是在IDE里手動添加JDK路徑到~/.sdkman/candidates/java/特定版本三是把JAVA_HOME明確指向~/.sdkman/candidates/java/current并寫進IDE的環(huán)境變量配置里。我個人比較喜歡第三種因為以后切換版本不用再回IDE改路徑。7.3 踩坑三切換版本后JAVA_HOME沒有變化有時候當(dāng)前目錄下執(zhí)行java -version顯示是Java 8但echo $JAVA_HOME還是指向Java 17的current你會覺得SDKMAN沒有生效。多數(shù)情況下是因為當(dāng)前shell加載SDKMAN初始化時JAVA_HOME被設(shè)置過一次靜態(tài)路徑而你手動用export JAVA_HOME...覆蓋過它。SDKMAN在use和default切換時會重新生成JAVA_HOME但如果之前手動導(dǎo)出了固定路徑就可能會覆蓋掉SDKMAN的動態(tài)設(shè)置。解決思路很簡單別在~/.bashrc或項目腳本里手動寫死JAVA_HOME的絕對路徑讓SDKMAN來管理。如果非要在腳本里指定那就在設(shè)置完JAVA_HOME之后重新source一次初始化腳本把current鏈接的路徑重新掛到環(huán)境變量上。7.4 踩坑四升級SDKMAN后本地版本丟失SDKMAN本身升級是一個常見的動作執(zhí)行sdk selfupdate后極少數(shù)情況下會把candidates/java/目錄下的部分內(nèi)容重置好在已安裝的JDK包并不會被刪除只是current鏈接恢復(fù)到了默認。這種情況直接在sdk list java里把之前安裝過的版本標記為已安裝再重新default一次就可以了。真正需要上心的是不要手動去改~/.sdkman/candidates/java/目錄下的文件結(jié)構(gòu)比如往里面塞手動解壓的文件夾否則很可能讓SDKMAN的版本檢測混亂。這么多年用下來我最深的一個體會是版本管理工具的價值不在于“能裝多個版本”而在于“切換成本足夠低”。當(dāng)“切一下環(huán)境”的成本從十分鐘變成一秒鐘你就會愿意為每個項目都準備一個獨立且干凈的運行環(huán)境而不是將所有項目都硬塞到同一個JDK上。如果你目前還在手動解壓、手動改PATH的階段我建議你花十分鐘把SDKMAN跑起來先把兩個版本裝好、切換試一遍然后配合.sdkmanrc給項目做自動環(huán)境綁定。這個小習(xí)慣能讓你在同時維護多個項目時把糾結(jié)環(huán)境變量的精力省下來好好聚焦代碼本身。