《Java 编程入门》2.1 安装 JDK 25:Temurin、PATH 与 JAVA_HOME

从 Temurin 25.0.5 的安装包选择讲起,说明 PATH 与 JAVA_HOME 各自影响哪个环节,如何在 macOS、Linux、Windows 上验证 java 与 javac 指向同一个 JDK;再在 library 工程里写一个 EnvironmentProbe,把版本、java.home 与编码打印出来,避开多 JDK 环境最常见的错配。

本节目标:在一台干净机器上装好 JDK 25,理解 PATH 与 JAVA_HOME 的分工,并能在多 JDK 环境里确认 java、javac、mvn 用的是同一个版本。
适用版本:JDK 25 LTS(Temurin 25.0.5);Maven 3.9.12。

先装 JDK,再装 IDE

很多教程的第一步是「下载 IntelliJ IDEA」,然后新建项目。这个顺序在个人学习时能跑通,但在团队协作里会留下隐患:IDE 可能自带一个 JDK,项目配置里又指定了另一个,命令行和 CI 再使用第三个。最后出现「IDE 里能跑、mvn 报错」时,排查成本远高于一开始就把版本对齐。

所以本章的顺序是:先装 JDK,再装 Maven,最后装 IDE。 IDE 只是编辑器加调试器,它不生产 JDK,也不应该成为版本的唯一来源。命令行能编译、能运行之后,再把 IDE 指向同一个 JDK,环境才是一套。

JDK 25 是 2025 年 9 月发布的 LTS。本书选用 Eclipse Temurin 的 25.0.5 构建,原因不是它有什么独家功能,而是它属于 OpenJDK 的免费发行版,构建来源透明,macOS、Linux、Windows 都有安装包,适合作为教学基线。Zulu、Corretto、Liberica 等同源发行版在卷一也能用,命令和 API 没有差别;换发行版时只需要重新确认 java -version 的输出。

Temurin 25 里有什么

下载页面上会同时出现 JRE、JDK、带安装器的包、压缩包几种选项。卷一需要的是完整 JDK,不是 JRE。原因在 1.3 已经讲过:javac、jshell、javap、jar 都在 JDK 里,JRE 只负责运行。

包类型包含适合谁
JDK + 安装器开发工具、运行时、系统集成本机开发,推荐
JDK 压缩包同上,解压即用服务器、CI、需要多版本并存
JRE只有运行时只运行、不编译的环境
jmods模块化运行时素材用 jlink 裁剪镜像

本书统一使用 JDK。即使生产环境只需要运行,也可以装完整 JDK 后只调用 java,或者在第 17 章用 jlink 裁一个最小运行时。不要为了「省空间」在生产装一个来历不明的 JRE,版本和补丁来源反而更难核对。

macOS

macOS 有两种常用做法。第一种是下载 .pkg 安装器,双击后装到 /Library/Java/JavaVirtualMachines/;第二种是下载 .tar.gz,解压到 ~/.jdks/ 这类目录,再自己配置环境变量。第二种更适合需要同时保留 JDK 21 和 JDK 25 的读者。

用 Homebrew 也可以:

brew install --cask temurin@25

安装完成后,/usr/libexec/java_home -V 会列出机器上所有 JDK。这个工具是 macOS 自带的,能帮你找到安装路径,但不会自动改变 PATH。

Linux

服务器上通常用压缩包,避免发行版仓库里的 JDK 版本过旧:

mkdir -p ~/.jdks
tar -xzf OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz -C ~/.jdks

解压后得到 ~/.jdks/jdk-25.0.5+8/。也有 apt/yum 仓库可用,但仓库更新通常滞后,且不同发行版的包名不一致。团队项目更稳的做法是把 JDK 版本写进 CI 镜像或构建脚本,而不是依赖某台机器的系统包。

Windows

Windows 用户下载 .msi 安装器,安装时勾选「Set JAVA_HOME variable」和「Add to PATH」。如果安装器没有勾选,需要手动在「系统属性 → 环境变量」里添加:

JAVA_HOME = C:\Program Files\Eclipse Adoptium\jdk-25.0.5.8-hotspot
PATH      = %JAVA_HOME%\bin;%PATH%

注意 JAVA_HOME 指向 JDK 根目录,不是 bin 目录。这个细节在后面配置 Maven 时会反复出现。

安装目录怎么选

JDK 装在哪里,决定了你以后怎么升级、怎么并存多个版本。三种常见位置各有取舍:

位置优点代价
系统目录(/Library/Java/...、/usr/lib/jvm/...)所有用户可见,IDE 容易自动发现升级需要管理员权限,多版本管理麻烦
用户目录(~/.jdks/、~/.sdkman/)不需要 sudo,版本可以并存需要自己配 JAVA_HOME,换用户不共享
项目目录(./.jdk/)项目和 JDK 绑定,完全可复现目录大,不适合提交到 Git

本书把 JDK 放在用户目录,library 项目通过 JAVA_HOME 和 Maven 配置找到它。这样同一台机器可以同时保留 JDK 21 做兼容性验证、JDK 25 做日常开发,不需要来回卸载。

不建议把 JDK 解压到项目仓库里再提交。JDK 压缩包解压后几百 MB,既不适合 Git,也会让「换一台机器」变成「重新下载一个大文件」。如果确实需要项目级隔离,用容器镜像或 CI 配置固定版本,比把二进制塞进仓库更干净。

验证安装:三个命令

装完之后不要急着写代码,先确认三个命令的输出。打开一个新的终端,让环境变量生效:

java -version
javac -version
mvn -version

本机 JDK 25 输出:

openjdk version "25.0.5" 2026-10-20 LTS
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing)
javac 25.0.5

java -version 的输出写到标准错误流,javac -version 写到标准输出,这是历史遗留差异,不影响使用。关键是两个版本号一致:25.0.5 对 25.0.5。

Maven 的输出要多看一行:

Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1)
Maven home: /Users/bingrong.yan/apache-maven-3.9.12
Java version: 25.0.5, vendor: Eclipse Adoptium, runtime: /Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home
Default locale: zh_CN_#Hans, platform encoding: UTF-8

Java version 和 runtime 这两行说明 Maven 实际用的是哪个 JDK。它读的是 JAVA_HOME,不是 PATH 里的 java。所以「java -version 是 25,但 mvn -version 显示 21」这种情况很常见,原因就是两个变量指向了不同地方。

PATH 与 JAVA_HOME 的分工

这两个变量经常被混为一谈,其实职责完全不同:

变量谁在读作用
PATHshell、系统决定你输入 java、javac 时执行哪个文件
JAVA_HOMEMaven、Gradle、IDE、启动脚本告诉工具「JDK 根目录在哪」
CLASSPATHjava、javac告诉 JVM 去哪里找用户类,现代项目很少手写

JVM 本身不读 JAVA_HOME。你直接执行 /path/to/jdk/bin/java 时,它不会关心这个变量。读它的是外层工具:Maven 用 JAVA_HOME/bin/java 启动构建,IDE 用它定位源码和调试器,Tomcat 这类脚本用它拼启动命令。

macOS 上可以这样设置:

export JAVA_HOME=$(/usr/libexec/java_home -v 25)
export PATH="$JAVA_HOME/bin:$PATH"

Linux 上直接指向解压目录:

export JAVA_HOME="$HOME/.jdks/jdk-25.0.5+8"
export PATH="$JAVA_HOME/bin:$PATH"

这两行通常写进 ~/.zshrc、~/.bashrc 或 ~/.profile。写完要新开终端,或者 source 一次,否则当前 shell 看不到。

一个容易忽略的细节:PATH 里 $JAVA_HOME/bin 必须排在 /usr/bin 前面。macOS 的 /usr/bin/java 是一个 stub,找不到 JDK 时会弹窗提示安装;Linux 上也可能有系统自带的旧版本。顺序错了,which java 就会指向另一个 JDK。

多个 JDK 时怎么确认

机器上装了 JDK 21 和 25 时,不要只看 java -version。用 which -a 把所有同名命令列出来:

which -a java javac

本机输出:

/Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home/bin/java
/usr/bin/java
/Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home/bin/javac
/usr/bin/javac

排在前面的就是当前生效的。如果 java 和 javac 的第一行不是同一个目录,说明 PATH 里混入了两个 JDK,后面一定会遇到「编译用 25、运行用 21」的怪问题。

还有一种情况:IDE 里配置的 JDK 和命令行不同。IDE 通常有自己的 SDK 设置,不会跟着 shell 的 PATH 走。判断方法是在 IDE 的终端里再跑一次 java -version,确认它和项目 SDK 一致。

在 library 里落地

环境确认完,开始搭 library 的目录。第 2 章还不写业务,只把源码目录和验证类放进去:

mkdir -p ~/code/library/src/main/java/com/plume/library
cd ~/code/library

目录名和包名必须对应:com.plume.library 对应 src/main/java/com/plume/library/。现在先手工建,2.2 会解释 Maven 为什么认这个结构。

写一个只读环境变量的探针类:

package com.plume.library;

public final class EnvironmentProbe {
    public static void main(String[] args) {
        System.out.println("java.version  = " + System.getProperty("java.version"));
        System.out.println("java.home     = " + System.getProperty("java.home"));
        System.out.println("java.vendor   = " + System.getProperty("java.vendor"));
        System.out.println("os.name       = " + System.getProperty("os.name"));
        System.out.println("file.encoding = " + System.getProperty("file.encoding"));
    }
}

编译并运行:

javac -d out src/main/java/com/plume/library/EnvironmentProbe.java
java -cp out com.plume.library.EnvironmentProbe

本机 JDK 25 输出:

java.version  = 25.0.5
java.home     = /Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home
java.vendor   = Eclipse Adoptium
os.name       = Mac OS X
file.encoding = UTF-8

这五行分别回答五个问题:语言版本对不对、运行时来自哪个目录、发行版是谁、操作系统是什么、默认编码是不是 UTF-8。java.home 尤其有用:它直接告诉你当前 java 命令属于哪个 JDK,比 PATH 的推断更可靠。

file.encoding 在 JDK 18 之后默认就是 UTF-8(JEP 400),所以这里显示 UTF-8 是预期结果。如果你的环境显示 GBK 或 ANSI_X3.4-1968,后面读写中文文件时会出现乱码,需要检查 LANG、LC_ALL 或 JVM 启动参数,而不是先怀疑代码。

-d out 的作用是把编译产物放到 out 目录,并按包名生成子目录。编译后目录结构是:

out/
└── com/
    └── plume/
        └── library/
            └── EnvironmentProbe.class

java -cp out 里的 out 是类路径根目录,不是 class 文件所在目录。JVM 会用全限定名 com.plume.library.EnvironmentProbe 去拼 out/com/plume/library/EnvironmentProbe.class。这两个路径只要错一层,就会得到 ClassNotFoundException。

再把第 1 章的入口类放进同一个目录,确认包结构和 -cp 对两个类都成立:

package com.plume.library;

public final class LibraryApp {
    public static void main(String[] args) {
        System.out.println("library: ready");
    }
}

一次编译两个文件,分别运行:

javac -d out src/main/java/com/plume/library/EnvironmentProbe.java \
  src/main/java/com/plume/library/LibraryApp.java
java -cp out com.plume.library.EnvironmentProbe
java -cp out com.plume.library.LibraryApp

本机输出:

java.version  = 25.0.5
java.home     = /Users/bingrong.yan/.jdks/jdk-25.0.5+8/Contents/Home
java.vendor   = Eclipse Adoptium
os.name       = Mac OS X
file.encoding = UTF-8
library: ready

两个类共用一个 out,共用一套包目录。第 2.2 节会把 javac 换成 Maven,但类路径的结构不会变。

用脚本固定开发环境

口头约定「我用 JDK 25」不够,换一台机器就会漂移。可以在 library 根目录放一个小的环境脚本,把版本要求写死:

#!/usr/bin/env bash
set -euo pipefail

export JAVA_HOME="${JAVA_HOME:-$HOME/.jdks/jdk-25.0.5+8/Contents/Home}"
export PATH="$JAVA_HOME/bin:$PATH"

actual="$(javac -version 2>&1 | awk '{print $2}')"
expected="25.0.5"
if [[ "$actual" != "$expected" ]]; then
  echo "expected javac $expected, got $actual" >&2
  exit 1
fi

echo "JAVA_HOME=$JAVA_HOME"
echo "javac=$actual"

执行 source env.sh 后,当前 shell 就切换到指定 JDK;如果版本不符,脚本直接退出,不会让你在半错的环境里继续工作。这个脚本只做环境检查,不做构建,所以不需要引入任何第三方工具。

脚本里的 awk 只取 javac -version 的第二个字段。JDK 25 的输出是 javac 25.0.5,两个字段;如果输出格式变化,脚本会报错而不是静默放过,这是有意的——环境检查宁可失败得明显。

env.sh 不提交 JAVA_HOME 的绝对路径也可以:用 ${JAVA_HOME:-...} 允许外部覆盖,CI 就可以把它指向容器里的 JDK。项目需要固定的是版本号,不是某一台机器的目录布局。

常见错误与边界

「JAVA_HOME 指到了 bin 目录。」 这是最高频的配置错误。JAVA_HOME 必须是 JDK 根目录,Maven 会在它后面拼 /bin/java。如果写成 .../Contents/Home/bin,Maven 启动时会直接拒绝:

The JAVA_HOME environment variable is not defined correctly,
this environment variable is needed to run this program.

「java 是 25,javac 是 17。」 两个命令来自不同 JDK,通常是因为 PATH 里有一个旧 JDK 排在前面,而 JAVA_HOME 又指向新 JDK。用 which -a java javac 对照,或者临时执行 type -a java javac。不要用「重装 JDK」解决配置问题。

「装完不新开终端。」 修改 ~/.zshrc 后,当前终端不会自动重新加载。source ~/.zshrc 可以临时生效,但 IDE 这类从图形界面启动的程序可能读不到 shell 的配置。IDE 里的 JDK 要在 IDE 自己的设置里指定。

「用系统包管理器装 JDK 25,结果装成了 21。」 有些发行版的默认仓库还没有 25,apt install openjdk-25-jdk 可能失败或落到旧版本。装完必须用 javac -version 验证,不能只看安装命令有没有报错。

小结

  • 顺序是 JDK → Maven → IDE;IDE 不应该成为 JDK 版本的唯一来源。
  • 卷一使用完整 JDK,不是 JRE;Temurin 25.0.5 是教学基线,同源发行版可以替换。
  • PATH 决定命令执行哪个文件,JAVA_HOME 决定 Maven、IDE 和脚本使用哪个 JDK,JVM 本身不读它。
  • 验证环境要看三个输出:java -version、javac -version、mvn -version 里的 Java version。
  • EnvironmentProbe 用 System.getProperty 读出 java.version、java.home、file.encoding,比猜 PATH 可靠。
  • JAVA_HOME 指向 JDK 根目录;-d out 生成包目录,-cp out 指定类路径根目录。

环境装好只是第一步。接下来要把「手工建目录、手工敲 javac」换成一套能被团队和 CI 复现的构建方式,这就是 Maven 和标准目录结构要解决的问题。

阅读导航:上一节:1.3 JVM、JRE、JDK 与字节码 · 下一节:2.2 Maven 与标准目录结构 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发