服务器上配 Java,很多人第一反应就是sudo apt install default-jdk,一行命令结束。但等真正遇到“这套中间件只认 Java 17,那套老项目还锁在 Java 8”的场景时,你会发现 apt 仓库里的版本根本不够用。手动安装 Java 这件事,平时看起来多余,真到上线前一天才发现自己并不完全会处理。手动装与彻底卸载,说到底不是解压和删目录的问题,而是搞清楚环境变量、系统链接、包管理机制这三者的关系。这篇内容就是围绕 Ubuntu 上手把手装 Java、再干干净净卸载 Java 的完整链路展开的,适合部署过 Java 应用的运维、后端开发,以及所有想真正掌控 JDK 位置和版本的人。
我最初也觉得手动安装是多此一举,直到在某台测试服务器上需要同时跑三个不同 JDK 版本的项目,才体会到手动管理的价值。而“完全卸载 Java”这件事更是坑——很多人删了目录、清完环境变量,结果java -version还能找到命令,或者卸载后系统里到处都是断链。这篇文章把我实际使用的流程、判据和踩坑点都写出来,不用去翻一堆零散资料。
1. 为什么放着 apt 不装,非要手动装 Java
1.1 仓库里的 Java 版本,不一定是项目要的版本
apt 装的default-jdk实际指向 Ubuntu 软件源里的某个 OpenJDK 版本,而这个版本跟着发行版走,不是你能随便选的。举个例子,某个 LTS 版本的 Ubuntu 可能默认带 OpenJDK 17,但你的生产环境规定必须用 Java 8 的特定小版本,或者客户那边指定了某个带安全补丁的 OpenJDK 更新。这时候 apt 仓库里的选项就很有限,除非你自己加第三方 PPA,但 PPA 的维护质量参差不齐,不如直接下载官方发布的 tar.gz,把版本控制权握在自己手里。
另一个常见场景是内网部署。很多企业的服务器不能随便访问外网,apt 源要么是镜像,要么是提前下好的离线包。手动安装的 tar.gz 包非常方便——在一台能上网的机器上下载好,拷贝到内网机器解压、配置环境变量即可,完全不依赖网络源。
1.2 手动安装与 apt 安装的实际差异
apt 安装本质上是把 JDK 当作系统软件包来管理,自带依赖关系、自动更新、卸载脚本。手动安装本质上是“把文件摆放到你指定的位置,然后自己告诉系统怎么用”,没有自动更新,也不会有包管理器帮你维护一致性。两者对比起来很直观:
| 维度 | apt 安装 | 手动 tar.gz 安装 |
|---|---|---|
| 版本选择 | 受仓库限制,通常只有某几个 LTS | 完全自由,可锁定任意版本 |
| 安装位置 | 系统固定(多为 /usr/lib/jvm) | 自定义目录,例如 /opt/java |
| JAVA_HOME 配置 | 常需另外设置 | 安装时可一并配置 |
| 多版本共存 | 可以用 alternatives,但系统包会互相牵制 | 多版本目录互不干扰 |
| 卸载 | apt remove/purge | 删目录 + 清配置 + 清链接 |
| 更新维护 | 跟随仓库更新 | 自己下载覆盖 |
这个差异也决定了你选哪条路。若是临时跑个 demo、随便编译一下,apt 肯定是最高效的;但如果你要在一台服务器上长期维护某个 Java 应用,或者同一台机器上要按项目切换 JDK,手动安装的灵活性和可控性优势非常明显。
1.3 这个做法适合谁,不适合谁
先说适合谁:在 Linux 服务器上部署过 Java 服务、维护过 Tomcat/Kafka 这类中间件、或者在 CI/CD 构建机上需要锁定 JDK 版本的工程师。这类人真正缺的不是“怎么执行命令”,而是“怎么在不同场景下选择合理的安装方式”。
不适合谁:只是想在 Ubuntu 桌面版上随便写个 HelloWorld、对版本没有要求、也不想管环境变量的新手。这类场景用 apt 装默认 JDK,然后把 JAVA_HOME 配上就完事,没必要引入手动管理的复杂度。
我的建议是,手动安装要学,但不是为了替代 apt,而是为了在需要的时候有一条可依赖的退路。你永远不知道哪天会碰上仓库里没有的版本。
2. 安装前必须敲定的三件事:版本、格式、目录
2.1 LTS 版本怎么挑(Java 8 / 11 / 17 / 21)
Java 的版本策略是每半年发一个 feature 版本,每三年发一个 LTS。普通版本只有半年维护,不适合生产;LTS 才有长期支持。目前还能在生产环境见到的 LTS 大体是 Java 8、11、17、21。怎么选,取决于你生态里的其他组件:
- Java 8:大量老项目、老中间件、遗留系统还锁在这个版本。比如很多企业内部的旧 Web 应用,或者某些老版本数据库驱动。
- Java 11:不少中间件默认支持,像是 Kafka、Elasticsearch 这类生态持续维护的版本,很多还在 Java 11 上跑得最稳。
- Java 17:目前的新项目主力。Spring Framework 6 和 Spring Boot 3 都基于 Java 17,等于说新开发的工程基本绕不开它。
- Java 21:较新的 LTS,虚拟线程、增强的 ZGC 等特性都在这个版本里,适合新启动、想用新特性的项目。
如果你只是在一台机器上做学习验证,选中一个 LTS 的最近版本就行,改动最小、坑最少。如果是要给某个具体项目配套,先看项目的 pom.xml、build.gradle 或者 docker 镜像里用的什么版本,跟着配套走,而不是自己拍脑袋选最新版。
2.2 tar.gz 与 deb:两种手动安装包的路线差异
你可能看到官网提供多种下载格式,其中最容易混淆的就是 tar.gz 和 deb。deb 包虽然也可以dpkg -i安装,但它是用 Debian 包管理逻辑来装 JDK 的,安装后会自动把文件放到 /usr/lib/jvm 下,有时还会自动触发 update-alternatives。这件看似方便的事,反而会干扰你后续的“彻底掌握路径”目标。
tar.gz 是纯解压式安装,你把它解压到哪个目录,Java 就在哪个目录,完全透明。卸载时删掉目录并清理引用即可,不会有任何包管理器残留。
我的习惯是,既然都选择手动安装了,就用最“原始”的 tar.gz。它能让你真正理解 JDK 安装的本质:无非是一堆二进制文件 + 库文件,加两个环境变量,让系统能找到它。deb 包适合想省点事、又不想用 apt 仓库版本的人,但它在“彻底卸载”环节会比 tar.gz 多一层 dpkg 状态要清理。
2.3 确认系统架构和你下载包的一致性
这一步看着简单,实际很多人踩坑。Intel/AMD 的服务器通常是 x86_64,ARM 云主机通常是 aarch64。如果下载包架构和系统架构不一致,解压后运行java -version会直接提示无法执行二进制文件,或者干脆没有输出。
确认架构只需要一条命令:
uname -m返回x86_64就下 x64 包,返回aarch64就下 aarch64 包。还有一点要留意:官网下载页面有时会有“x64 compressed archive”和“aarch64 compressed archive”,别只看文件名带不带linux就下手。
下载时建议先校验一下 SHA256 校验和,官方页面会提供对应的哈希值。命令如下:
sha256sum jdk-17_linux-x64_bin.tar.gz对比输出与页面提供的哈希值一致再使用。这一步在离线内网环境下尤其重要——你无法确保拷贝过来的文件完整无损。
3. 手动安装完整流程:从解压到环境变量生效
3.1 选定安装目录与解压
安装目录的选择是第一步。常见方案有两个:一个是/usr/lib/jvm,这是 Ubuntu 上 apt 包默认放 JDK 的目录;另一个是/opt/java或/usr/local/java,更适合自己手动管理的 JDK。
我更推荐/opt/java。原因很简单:放在 /usr/lib/jvm 下,容易被系统和后续的包管理扫描到,造成“这是我用 apt 装的”的错觉;放在 /opt 下,一眼就能看出这是手动管理的软件,卸载时不会误伤系统包。
sudo mkdir -p /opt/java sudo tar -zxvf jdk-17_linux-x64_bin.tar.gz -C /opt/java解压后目录名一般是jdk-17.0.x这种带具体小版本号的形式。为了以后升级方便,建议建一个不带版本号的软链接指向当前版本,比如:
sudo ln -s /opt/java/jdk-17.0.12 /opt/java/current这样 JAVA_HOME 可以始终指向/opt/java/current,将来替换小版本时,只需更新软链接,不需要改环境变量。这个习惯在升级 JDK、回滚版本时非常省事。
3.2 配置环境变量,而不是照抄老教程的 CLASSPATH
环境变量配置是整个手动安装里最容易出错、也最需要理解的一环。Java 在 Linux 上核心要关注的是JAVA_HOME和PATH。JAVA_HOME用于告诉后续的 Maven、Gradle、Tomcat、IDEA 等工具 JDK 的根目录;PATH用于让 shell 直接找到java、javac等命令。
千万别照抄老教程里那一大段CLASSPATH=.:$JAVA_HOME/lib/tools.jar之类的配置。那是很多年前的套路,现在的 JDK 早就不是靠手动设置 CLASSPATH 来加载标准库了。多余的 CLASSPATH 反而会污染你的项目依赖,导致某些 jar 冲突。
推荐在/etc/profile.d/下新建一个独立的脚本文件,例如java.sh:
sudo vim /etc/profile.d/java.sh内容如下:
export JAVA_HOME=/opt/java/current export PATH=$JAVA_HOME/bin:$PATH选择 /etc/profile.d/ 而不是直接改 /etc/profile,是因为系统在用户登录时会自动执行该目录下的所有 .sh 文件,这样配置是系统级的,对所有用户生效,而且单独一个文件便于将来卸载时直接删掉。如果你只想对当前用户生效,就把同样的内容写到~/.bashrc或~/.profile里。
写完后执行:
source /etc/profile.d/java.sh然后验证:
java -version javac -version echo $JAVA_HOME如果你开了一个新终端窗口,登录 shell 或交互式 shell 会重新加载环境变量,这种情况下不用手动 source。
3.3 让 java 命令全局可用:PATH 与 update-alternatives
配置 PATH 后,java命令已经能在绝大多数交互式 shell 里使用了。但还有一个隐患:很多软件(尤其是 Debian/Ubuntu 系的服务管理脚本)会优先在/usr/bin下找java。如果你的 PATH 没有覆盖某个服务的环境,服务可能仍然找不到 JDK。
解决办法是把 Java 注册到系统 alternatives 机制里。这个机制类似于“快捷键管理器”:系统在 /usr/bin/java 放一个软链接,指向 /etc/alternatives/java,后者再指向你实际的 JDK 路径。注册命令如下:
sudo update-alternatives --install /usr/bin/java java /opt/java/current/bin/java 100 sudo update-alternatives --install /usr/bin/javac javac /opt/java/current/bin/javac 100数字 100 是优先级,数字越大,在自动选择模式下越优先。执行完后:
sudo update-alternatives --config java可以查看当前系统所有已注册的 JDK,并手动切换。这一步做完之后,which java会显示/usr/bin/java,但readlink -f $(which java)最终会指向/opt/java/current/bin/java,这就是 alternatives 在中间做了一层托管。
4. 多版本 Java 共存与切换:熟练使用 alternatives
4.1 一个系统里两套 JDK 的典型场景
你可能觉得一台机器装多个 JDK 有点过度,但实际场景非常常见:某收费系统的编译环境必须用 Java 8,旁边的数据同步服务又要求 Java 17;或者你本地要同时维护两个开源项目,一个项目的 CI 脚本写死了 JDK 11,另一个用 JDK 21。这种情况下,与其反复安装卸载,不如让它们共存,随时切换。
手动安装的目录结构天然适合共存。每个版本一个目录,互不影响。真正的关键是切换机制,而 update-alternatives 就是解决这个问题的官方工具。
4.2 注册与切换:核心是优先级数字
把两个版本的 JDK 都注册到 alternatives:
sudo update-alternatives --install /usr/bin/java java /opt/java/jdk-8/bin/java 80 sudo update-alternatives --install /usr/bin/java java /opt/java/jdk-17/bin/java 100 sudo update-alternatives --install /usr/bin/javac javac /opt/java/jdk-8/bin/javac 80 sudo update-alternatives --install /usr/bin/javac javac /opt/java/jdk-17/bin/javac 100优先级数字高的是默认自动模式下的选择。需要临时切换时:
sudo update-alternatives --config java sudo update-alternatives --config javac会看到一个交互式列表,输入编号即可切换当前生效的版本。这种切换是系统级的,会直接影响所有从 /usr/bin/java 调用的程序。
4.3 切换后验证时容易忽略的小细节
切换 alternatives 只是一半,很多人的 JAVA_HOME 还是自己在环境变量里写死的。这会导致一个很怪的现象:你执行java -version是 Java 17,但 Maven 或 Gradle 通过 JAVA_HOME 拿到的还是 Java 8。
所以多版本切换时,JAVA_HOME 也要跟着变。比较实用的做法是把 JAVA_HOME 也通过 alternatives 管理,或者干脆在切换脚本里同时更新环境变量。我自己习惯写两个 shell 函数或 alias:
alias jdk8='sudo update-alternatives --set java /opt/java/jdk-8/bin/java && sudo update-alternatives --set javac /opt/java/jdk-8/bin/javac && export JAVA_HOME=/opt/java/jdk-8' alias jdk17='sudo update-alternatives --set java /opt/java/jdk-17/bin/java && sudo update-alternatives --set javac /opt/java/jdk-17/bin/javac && export JAVA_HOME=/opt/java/jdk-17'切换完之后,用这三条命令验证一致性:
java -version echo $JAVA_HOME readlink -f $(which java)前两条确认 Java 版本和 JAVA_HOME 指向一致,第三条确认实际调用的是哪个目录下的二进制。三个输出都对上,才算切换成功。
5. 完全卸载 Java:把文件、配置、链接一次清干净
5.1 第一步永远是确认自己卸的是哪套 Java
卸载 Java 最忌讳的就是凭印象动手。很多人只看which java输出一个路径就开始rm -rf,结果删掉的是系统包的目录,或者把一个被其他软件依赖的 JDK 删了。所以在动任何文件之前,先搞清楚系统里有哪些 Java、当前生效的是哪一个:
which java readlink -f $(which java) java -version如果/usr/bin/java是一个软链接,readlink -f会一路穿透到最终的 JDK 目录。这就能立刻判断当前生效的 Java 是不是你要卸载的那个。
同时检查 apt 包管理器的视角:
dpkg -l | grep -E "openjdk|jdk|jre"这条命令列出所有通过 apt/dpkg 安装的 Java 相关包。只有当你确认目标 JDK 是手动安装的,才走手动静默删除的路线。如果是 apt 装的,应该用 apt remove 而不是直接删目录。
5.2 文件层清理:目录、软链接、alternatives
手动静默安装的 Java,卸载时按“链接 -> 目录 -> 环境变量”的顺序清理,也就是先把系统里所有指向它的引用断开,再删实体文件。
第一步,从 alternatives 里移除注册信息:
sudo update-alternatives --remove java /opt/java/jdk-17/bin/java sudo update-alternatives --remove javac /opt/java/jdk-17/bin/javac如果不确定当时的注册路径,可以先执行update-alternatives --display java查看,再针对具体路径移除。如果该注册项是唯一的,移除后 /usr/bin/java 这个链接会被直接删除。
第二步,删除软链接和实际目录:
sudo rm -f /opt/java/current sudo rm -rf /opt/java/jdk-17.0.12注意,如果current指向的目录还没删,不要急着删软链后再删目录,顺序反了也会留下悬空链接。先删软链、再删真实目录,是最稳妥的。
第三步,清理环境变量配置文件。你需要检查这些可能写入过 JAVA_HOME 的文件:
/etc/profile.d/java.sh(最可能)/etc/environment/etc/profile~/.bashrc~/.profile~/.zshrc
用编辑器打开,把和 JAVA_HOME、PATH 加进去的相关行删掉。如果之前是通过/etc/profile.d/java.sh配置的,直接删除整个文件即可,这是把环境变量写在独立文件里的最大好处。
sudo rm /etc/profile.d/java.sh5.3 用户级缓存与残留配置
文件系统和环境变量清理完后,还有一类容易被忽视的残留:用户目录下的 Java 缓存和配置。虽然它们不影响java -version是否存在,但如果你追求“完全卸载”,这些也应该看一遍。
最常见的是~/.java目录,里面有.userPrefs之类的用户偏好配置。还有/tmp下的hsperfdata_*目录,这是 Java 进程运行时的性能数据残留。进程结束后这些文件一般会残留一部分。
rm -rf ~/.java sudo rm -rf /tmp/hsperfdata_*另外如果你用过某个 IDE 或构建工具,可能在~/.config下有相关配置,但这些不止属于 JDK,卸载 Java 时不用动它们,删了反而影响其他软件。
5.4 卸载不该碰的系统 Java
这条非常重要:Ubuntu 上不少软件依赖系统自带的 OpenJDK,直接删系统 JDK 会连带搞坏其他软件。典型的是 LibreOffice,以及某些桌面组件。在卸载之前,可以先反查依赖关系:
apt-cache rdepends default-jre apt-cache rdepends openjdk-17-jre如果某个核心软件依赖它,卸载后会有提示或直接导致功能缺失。我的经验是,如果系统里存在通过 apt 安装的 OpenJDK,但当前生效的手动 JDK 指向的是/opt/java,你完全可以用手动方式把/opt/java下的 JDK 清掉,保留 apt 版不动。这样既满足了“彻底卸载目标 JDK”,又不会破坏系统依赖。这个“只动自己装的、保留系统管理的”原则,是卸载阶段最核心的纪律。
如果你决定要整台服务器上一个 Java 都不留,包括 apt 包的 OpenJDK,那正确的卸载方式是用 apt:
sudo apt purge openjdk-17-jdk openjdk-17-jre而不是rm -rf /usr/lib/jvm/java-17-openjdk-amd64。直接删 apt 包的文件,dpkg 数据库里会留下不一致状态,之后每次 apt 操作都可能有异常提示,修起来比卸载本身还麻烦。
6. 卸载后自检清单与常见坑
6.1 三条命令确认“真没了”
卸载完成后,别急着收工,用下面三条命令确认。
which java || echo "java not found" readlink -f $(which java) 2>/dev/null || echo "no java binary" java -version 2>&1 || echo "java command not available"which java如果没有任何输出,说明 PATH 里已经找不到 java 命令;java -version如果报 command not found,说明 shell 里也调用不到了。再去环境变量配置文件里确认没有 JAVA_HOME 的残留:
echo $JAVA_HOME如果输出为空,环境变量清理也到位了。最后再用 find 扫一下常见的 JDK 目录,确认没有遗留的压缩包或解压目录:
find /opt /usr/local /usr/lib/jvm -maxdepth 2 -iname "*jdk*" 2>/dev/null这一套检查做完,基本可以放心了。
6.2 为什么卸载后 echo $JAVA_HOME 还有值
很多人遇到过:明明删掉了/etc/profile.d/java.sh,新开一个终端echo $JAVA_HOME竟然还有旧值。原因多半是你启动的终端是从一个已经登录的会话继承来的,或者有其他配置文件里也写入了 JAVA_HOME。
检查完环境变量,排除旧 shell 会话问题最简单的方式是重开一个终端窗口,或者重新登录 SSH。如果新终端里 JAVA_HOME 还有值,就去~/.bashrc、~/.profile、~/.zshrc、/etc/environment这些文件里逐个排查。有时候.bashrc里会有一行source /etc/profile.d/java.sh,就算原文件删了,这行残留会把一个不存在的文件反复加载,虽然没有实际值,但会在终端启动时产生报错信息。
环境变量是按“登录 shell 配置 + 交互式 shell 配置”叠加的,优先级大体是/etc/environment</etc/profile+/etc/profile.d/*.sh<~/.profile<~/.bashrc。排查时从优先级低的开始看,逐步缩小范围。
6.3 遇到“断链”的修复思路
如果你先删了 JDK 的真实目录,却没先从 alternatives 里移除注册,就会出现一个典型的“断链”状态:/usr/bin/java还在,但readlink -f /usr/bin/java显示目标目录已不存在。此时执行java -version会提示“No such file or directory”。
修复思路很简单:如果确认不要再装这个 JDK,就把断掉的 alternatives 注册项清掉。仍然需要确认当时的注册路径,然后执行:
sudo update-alternatives --remove java /opt/java/jdk-17/bin/java如果不知道路径,可以先执行:
sudo update-alternatives --display java它会列出所有注册项的路径,其中路径已失效的那个就是要清理的。如果 alternatives 列表里一项都不想要了,也可以直接sudo update-alternatives --remove-all java,一键清空所有 java 注册项。
6.4 关于 apt 与手动安装,我的最终判断
折腾了这么多轮,我对 apt 和手动安装的最终判断很明确:日常快速验证用 apt,正式环境、多版本管理场景用手动安装。apt 的 default-jdk 适合“只要是个 Java 能跑就行”;手动安装适合“我知道我装的是哪个版本、放在哪个目录、以后怎么卸”。这两条路并不冲突,关键是你得知道当前系统里哪个 Java 是 apt 管的、哪个是自己放的,以及切换/卸载时应该走哪套流程。
手动安装也好、彻底卸载也罢,说到底都是把 JDK 当成一个普通软件来对待:文件放哪里、环境变量让谁找到它、系统链接怎么指过去。只要这三层都理清楚了,装 Java 和卸 Java 就不再是照着教程敲命令,而是变成了你对这台服务器的一种掌控感。希望这篇内容能帮你把这一步走稳。