1. 为什么你需要这篇离线部署与多版本管理指南
最近在帮一个朋友的公司处理他们内部开发服务器的Java环境,遇到了一个挺典型的场景。他们的服务器部署在内网,完全没法访问外网,但项目组那边有几个老项目必须跑在JDK 11上,而服务器上默认安装的是JDK 17。开发同学急得团团转,因为项目编译不过,测试也跑不起来。这让我想起,其实很多公司的生产环境、测试环境,或者一些对安全要求极高的研发内网,都是这种“离线”状态。你不能简单地敲个 sudo apt install openjdk-11-jdk 就完事儿,因为压根连不上Ubuntu的软件源。
所以,今天我想跟你详细聊聊,怎么在Ubuntu系统里,纯手工、离线地把OpenJDK 11给装好,并且还能优雅地管理多个JDK版本,比如在11和17之间来回切换。这不仅仅是把压缩包解压那么简单,里面涉及到安装路径的规范、系统命令的注册、环境变量的配置,还有后续维护的便捷性。我自己在运维和开发混合的角色里摸爬滚打这么多年,发现一套清晰的手动部署流程,远比依赖包管理器更可控,尤其是在环境隔离和版本固化要求高的场景下。无论你是运维工程师需要批量部署,还是开发人员搭建自己的开发机,这套方法都能让你心里有底,彻底摆脱网络依赖。
2. 动手之前:准备工作与核心概念理解
2.1 获取正确的离线安装包
万事开头难,第一步找对安装包就成功了一半。你不能随便从网上下个JDK就往上扔,得找对地方。我强烈推荐从 Adoptium(以前叫AdoptOpenJDK,现在项目叫Eclipse Temurin)下载。为什么是它?因为它是Eclipse基金会旗下的,提供了经过全面测试、高质量、跨平台的Java运行时,社区活跃,版本更新也及时,用起来比较放心。
具体怎么操作呢?你需要找一台能上网的电脑,打开Adoptium的官网。选择版本为“11”,包类型选择“JDK”(别选成JRE了,JRE只能运行Java程序,不能编译),操作系统选“Linux”,架构根据你的服务器CPU来,现在基本都是“x64”。最重要的是,包格式一定要选 .tar.gz 或者 .tar.xz。这两种是标准的Linux压缩归档格式,我们后面要用命令行解压。避免下载 .deb 或 .rpm 包,因为离线环境下处理这些包的依赖关系会是一场噩梦。下载完成后,你会得到一个类似 OpenJDK11U-jdk_x64_linux_hotspot_11.0.23_9.tar.gz 的文件,文件名里包含了版本和构建号。
2.2 将安装包传输到目标服务器
包下载好了,怎么弄到那台离线的Ubuntu服务器上呢?方法有很多,你可以根据实际情况选:
- U盘/移动硬盘:最物理、最安全的方式,适合机房直连。
- SCP命令:如果你有一台跳板机(堡垒机)能同时访问互联网和内部网络,可以用
scp命令安全地复制过去。命令大概长这样:scp /本地路径/下载的jdk包.tar.gz 用户名@目标服务器IP:/home/用户名/。 - 内部文件服务器或NAS:很多公司有内部的文件共享服务,上传后再从服务器下载。
我个人的习惯是,先在服务器的用户家目录(比如
/home/yourname/)下建一个临时文件夹,比如叫softwares,把所有需要离线安装的包都扔进去,这样管理起来比较清晰。假设现在安装包已经安静地躺在~/softwares/目录下了。
2.3 理解 update-alternatives:多版本管理的灵魂
在开始安装前,我们必须先搞懂一个Ubuntu/Debian系统自带的神器:update-alternatives。你可以把它理解成一个“系统命令的版本开关管理器”。Linux系统里,像 java、javac 这些命令,其实都是放在 /usr/bin/ 这类标准路径下的一个符号链接。update-alternatives 就是管理这些链接最终指向哪个实际可执行文件的工具。
举个例子,系统里可能同时存在 /usr/lib/jvm/jdk-11/bin/java 和 /usr/lib/jvm/jdk-17/bin/java 两个真实的Java程序。/usr/bin/java 这个大家都能用的命令,到底该指向谁呢?update-alternatives 维护了一个“候选列表”,你可以通过它来注册不同的版本,并设置优先级。系统会自动选择优先级最高的版本作为“自动模式”的默认选项,你也可以手动指定一个。这样一来,切换Java版本就不再需要去手动修改环境变量 PATH 或者创建软链接了,非常优雅和系统化。我们后续的操作会紧紧围绕这个工具展开。
3. 步步为营:离线安装OpenJDK 11全流程
3.1 创建标准的JDK安装目录
Linux系统讲究个“各就各位”,软件安装到约定俗成的目录下,后续管理会省心很多。对于JDK,公认的标准位置是 /usr/lib/jvm/。这个目录就是专门用来存放各种Java虚拟机(JVM)实现的,比如Oracle JDK, OpenJDK, Amazon Corretto等都会放在这里。
首先,我们检查一下这个目录是否存在,如果不存在就创建它。打开终端,执行:
sudo mkdir -p /usr/lib/jvm
这里的 -p 参数很贴心,意思是如果路径中的父目录(比如 /usr/lib)不存在,也会一并创建。不过对于Ubuntu系统,/usr/lib 目录通常都在,所以这个命令主要是确保 /usr/lib/jvm 存在。
3.2 解压安装包到目标位置
现在,我们把下载好的压缩包解压到刚刚创建的标准目录。假设你的压缩包在 ~/softwares/ 下,名字是 OpenJDK11U-jdk_x64_linux_hotspot_11.0.23_9.tar.gz。
sudo tar -xzf ~/softwares/OpenJDK11U-jdk_x64_linux_hotspot_11.0.23_9.tar.gz -C /usr/lib/jvm
让我解释一下这个命令:
sudo:因为要写入/usr/lib/jvm系统目录,需要管理员权限。tar:归档工具。-xzf:三个选项的组合。x表示解压,z表示处理gzip压缩(如果是.tar.xz格式则用-xJf,J代表xz压缩),f后面指定文件名。-C /usr/lib/jvm:指定解压的目标目录为/usr/lib/jvm。
解压完成后,用 ls /usr/lib/jvm 命令看一下,应该会多出一个目录,名字可能就是压缩包解压后的名字,比如 jdk-11.0.23+9。这个名字有点长,为了方便后续管理和配置,我习惯给它改个简短通用的名字:
sudo mv /usr/lib/jvm/jdk-11.0.23+9 /usr/lib/jvm/jdk-11
现在,你的OpenJDK 11就完整地安装在 /usr/lib/jvm/jdk-11 目录里了。你可以进去看看,里面 bin、lib、include 等子目录一应俱全。
3.3 使用 update-alternatives 注册JDK命令
这是最关键的一步,我们要告诉系统:“喂,我这儿新装了个Java,它的命令在这里,你把它记下来,以后可以考虑用它。”
我们为最常用的三个命令 java(运行)、javac(编译)、jar(打包)进行注册。注意,每个命令都需要单独注册。
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-11/bin/java 1111
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk-11/bin/javac 1111
sudo update-alternatives --install /usr/bin/jar jar /usr/lib/jvm/jdk-11/bin/jar 1111
来拆解一下 --install 参数:
- 第一个参数(
/usr/bin/java):是系统级命令链接的位置。 - 第二个参数(
java):是update-alternatives系统中这个“命令组”的名字。 - 第三个参数(
/usr/lib/jvm/jdk-11/bin/java):是我们刚刚安装的JDK中该命令的真实路径。 - 第四个参数(
1111):优先级,这是一个整数。当系统处于“自动模式”时,会选择优先级最高的版本作为默认项。这里我给JDK 11设了一个很高的优先级1111,是为了确保在自动模式下它能胜出。如果你装了JDK 17并设置了优先级1200,那么17的优先级更高。
你可以根据需要,继续注册其他工具,比如 javadoc、jps、jstack 等,命令格式一模一样,只需替换命令名和路径即可。
3.4 切换并验证默认版本
注册完之后,我们来选择默认版本。如果你系统里之前没有其他JDK,那么JDK 11现在就是唯一选项,也是默认选项。但如果之前有(比如系统自带的OpenJDK 17),我们就需要手动切换一下。
运行以下命令进行交互式选择:
sudo update-alternatives --config java
你会看到一个数字列表,大概长这样:
有 2 个候选项可用于替换 java (提供 /usr/bin/java)。
选择 路径 优先级 状态
------------------------------------------------------------
* 0 /usr/lib/jvm/jdk-17/bin/java 1171 自动模式
1 /usr/lib/jvm/jdk-11/bin/java 1111 手动模式
2 /usr/lib/jvm/jdk-17/bin/java 1171 手动模式
要维持当前值[*]请按<回车键>,或者键入选择的编号:
这里,* 号表示当前默认的。因为JDK 17的优先级是1171,高于我们的1111,所以自动模式下它被选中了。我们输入编号 1,然后回车,就将默认的 java 命令切换到了JDK 11。同样地,对 javac 也执行一遍:
sudo update-alternatives --config javac
选择对应的JDK 11编号。
完成之后,立刻验证一下:
java -version
javac -version
如果终端打印出的信息明确显示“OpenJDK 11.0.23”,那么恭喜你,核心的安装和切换已经成功了!系统现在调用 java 和 javac 时,使用的都是我们刚刚离线安装的JDK 11。
4. 让环境“认识”你的JDK:配置JAVA_HOME
虽然通过 update-alternatives 已经能让命令行工具找到正确的JDK,但对于很多Java应用(比如Maven、Gradle、Tomcat、Jenkins等)以及一些IDE来说,它们还依赖一个非常重要的环境变量——JAVA_HOME。这个变量指向JDK安装的根目录,是许多Java相关软件寻找编译器、工具库的标准方式。
4.1 全局配置(推荐给服务器)
对于服务器环境,我们希望所有用户、所有服务都能使用同一个JDK配置,所以最好进行全局设置。全局环境变量通常在 /etc/environment 文件里配置。这个文件很简单,就是键值对的列表。
用你喜欢的文本编辑器(比如 nano 或 vim)打开它:
sudo nano /etc/environment
在文件末尾,添加这么一行:
JAVA_HOME="/usr/lib/jvm/jdk-11"
注意:路径不要带斜杠结尾,也不要使用 $JAVA_HOME 这样的变量引用,这里就是写死的绝对路径。保存并退出编辑器(在nano里是按 Ctrl+X,然后输入 Y 确认,再按回车)。
关键点来了:修改 /etc/environment 后,不会立即生效。你需要重启系统,或者至少让所有用户注销并重新登录,这个变量才会被加载到所有用户的会话中。这是因为它是在用户登录初期被读取的。所以,配置完后记得重启一下服务器,或者如果是在当前会话测试,可以先用下面的用户级配置方法临时解决。
4.2 用户级配置(适合个人开发机)
如果你只是在自己的开发机上操作,或者想立刻在当前终端生效,可以修改用户家目录下的 shell 配置文件,比如 ~/.bashrc(如果你用的是Bash)。
nano ~/.bashrc
滚动到文件最末尾,添加以下几行:
export JAVA_HOME="/usr/lib/jvm/jdk-11"
export PATH="$JAVA_HOME/bin:$PATH"
第一行设置了 JAVA_HOME 变量。第二行把JDK的 bin 目录添加到了 PATH 环境变量的最前面($PATH 代表原有的路径)。这样做的目的是确保当你输入 java 命令时,系统会优先使用我们设置的JDK 11下的命令,这是一个双保险。
保存文件后,要让这个配置在当前终端立刻生效,需要执行:
source ~/.bashrc
然后,你可以用 echo $JAVA_HOME 命令来检查是否设置成功,应该会输出 /usr/lib/jvm/jdk-11。
5. 进阶玩法:游刃有余地管理多个JDK版本
现在,JDK 11已经安装并设为默认了。但我们的目标是“管理多版本”。假设过段时间,有个新项目要求使用JDK 17,我们该怎么办?很简单,重复上面的离线安装流程,把JDK 17也安装到 /usr/lib/jvm/jdk-17 目录下。
5.1 注册与切换的日常
安装好JDK 17后,同样用 update-alternatives --install 命令进行注册,只是路径和优先级要变一下。比如,给JDK 17设置一个更高的优先级1200:
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-17/bin/java 1200
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk-17/bin/javac 1200
注册后,切换版本就变得异常轻松。任何时候,你只需要运行:
sudo update-alternatives --config java
sudo update-alternatives --config javac
然后在交互界面里,选择对应版本的编号,回车即可。整个过程不需要修改任何已配置好的 JAVA_HOME(如果你按照全局方式配置了,那需要根据情况修改 /etc/environment;如果是在 .bashrc 里,可以随时编辑切换)。对于只在命令行编译运行的项目,用 update-alternatives 切换就足够了。
5.2 针对不同项目的灵活配置
在实际开发中,我们经常遇到不同项目需要不同JDK版本的情况。全局切换虽然简单,但不够精细。这里分享两个更灵活的方法:
方法一:Shell脚本封装
为每个项目创建一个简单的启动脚本。例如,在项目A的根目录下创建一个 dev-env.sh 文件:
#!/bin/bash
# 为当前Shell会话临时切换到JDK 11
export JAVA_HOME=/usr/lib/jvm/jdk-11
export PATH=$JAVA_HOME/bin:$PATH
echo "Switched to JDK 11 for Project A."
在终端里,先 source dev-env.sh,然后再运行 mvn clean install 或 ./gradlew build,这个终端窗口里的所有操作就都会基于JDK 11了。另一个终端窗口则不受影响。
方法二:利用IDE或构建工具 现代工具本身就有很强的版本管理能力。
- IntelliJ IDEA:可以在
File -> Project Structure -> Project和Modules里,分别为每个项目指定不同的“Project SDK”和“Module SDK”。 - Eclipse:在
Window -> Preferences -> Java -> Installed JREs中添加多个JRE,然后在项目属性Java Build Path中指定。 - Maven:可以在
pom.xml中配置maven-compiler-plugin,指定source和target版本,但更彻底的方案是使用 Toolchains 功能,它允许Maven根据项目要求自动选择对应版本的JDK。 - Gradle:在
build.gradle文件中,可以通过java { toolchain { languageVersion = JavaLanguageVersion.of(11) } }来指定编译所需的JDK版本,Gradle会自动下载或使用本地的匹配版本。
这些方法都能实现项目级别的JDK隔离,比全局切换更加专业和可控。我个人的习惯是:在服务器上通过 update-alternatives 管理一个“基准”版本,在开发机上则主要依靠IDE和构建工具来管理,两者结合,天下无敌。
6. 避坑指南与最佳实践
踩过坑才知道路平。这里总结几个我遇到过的问题和注意事项,希望能帮你节省时间。
坑一:解压后目录权限问题
用 sudo 解压到 /usr/lib/jvm 下的目录,其所有者通常是 root。这没问题,因为JDK本身是只读的。但要确保 bin 目录下的可执行文件(如 java)有执行权限(ls -l 查看,应有 x 标志)。通常从官网下载的包权限都是正确的。如果遇到权限问题,可以用 sudo chmod -R 755 /usr/lib/jvm/jdk-11 来修正。
坑二:JAVA_HOME 与 update-alternatives 的混淆
JAVA_HOME 是给应用程序找JDK用的。update-alternatives 是给系统命令行找 java 命令用的。两者目的不同,但通常需要配合设置。确保 JAVA_HOME 指向的路径,就是 update-alternatives 里注册的那个JDK的根目录,不要指到 bin 子目录里去了。
坑三:Shell配置不生效
如果你修改了 ~/.bashrc,一定要记得 source ~/.bashrc 或者新开一个终端标签页。如果你修改了 /etc/environment,必须重启或重新登录。这是最容易让人困惑的地方,很多人改了 /etc/environment 后直接在原终端里 echo $JAVA_HOME,发现没变,就以为配置错了。
最佳实践建议:
- 路径标准化:始终坚持把第三方手动安装的JDK放在
/usr/lib/jvm/下,并以jdk-版本号这样的清晰命名。这会让后续维护和脚本编写清晰很多。 - 优先级规划:为
update-alternatives设置优先级时,可以有个简单的规则,比如用主版本号*1000 + 次版本号。JDK 11可以设1111,JDK 17设1711,JDK 21设2111。这样一看优先级就知道对应哪个版本。 - 记录与文档:在服务器或团队Wiki上,记录下安装了哪些JDK版本、安装路径、以及默认版本是什么。特别是当服务器由多人维护时,这份记录能避免很多“我机器上好好的”这类问题。
- 考虑使用版本管理脚本:如果你需要频繁在多版本间切换,可以写一个简单的Shell函数放到
.bashrc里,比如jdk11() { export JAVA_HOME=/usr/lib/jvm/jdk-11; ... },jdk17() { ... },这样切换就是一键的事。
手动离线部署和管理JDK,看似比一条安装命令麻烦,但它给了你完全的控制权和清晰的环境状态。尤其是在生产环境和定制化开发环境中,这种“一切尽在掌握”的感觉,会让你在排查问题时更有底气。希望这份超详细的指南能成为你工具箱里的一份实用手册。
360




被折叠的 条评论
为什么被折叠?



