Java 编译运行体系 & Maven 知识总结

本文从「JDK 是什么」讲到「Maven 如何调用编译器」,把 Java 的编译、运行、版本关系和 Maven 的工作原理串成一条完整的链路。适合想彻底搞懂构建底层机制的开发者。


目录

  1. [JDK、JRE、javac、java 到底是什么](#JDK、JRE、javac、java 到底是什么)
  2. [Java 的两步:编译与运行](#Java 的两步:编译与运行)
  3. [核心:字节码版本与 JVM 向下兼容](#核心:字节码版本与 JVM 向下兼容)
  4. [控制编译版本:source/target 与 release](#控制编译版本:source/target 与 release)
  5. [Maven 的本质:一个调用工具链的 Java 进程](#Maven 的本质:一个调用工具链的 Java 进程)
  6. [版本一致性:为什么 Maven 的 JDK 要和项目对齐](#版本一致性:为什么 Maven 的 JDK 要和项目对齐)
  7. 常见错误速查
  8. [一图总览 & 核心结论](#一图总览 & 核心结论)

一、JDK、JRE、javac、java 到底是什么

这是所有问题的地基,先分清楚三个东西的包含关系:

复制代码
JDK(Java Development Kit,开发工具包)
 ├── javac      ← 编译器:.java → .class
 ├── jar        ← 打包工具:.class + 资源 → .jar
 ├── javadoc    ← 文档生成工具
 └── JRE(Java Runtime Environment,运行环境)
      └── java(JVM)  ← 运行 .class
名称 是什么 能编译吗 能运行吗
JDK 开发全套:编译工具 + 运行环境 ✅ 有 javac ✅ 有 JVM
JRE 只含运行环境 ❌ 没有 javac ✅ 有 JVM
javac 编译器,属于 JDK ------ ------
java JVM 启动器,运行字节码 ------ ------

关键认知

  • javac 是 JDK 的一部分 ,所以 javac 的版本就等于 JDK 的版本。装 JDK 8,里面就是 JDK 8 的 javac;装 JDK 17,就是 JDK 17 的 javac
  • 一台机器可以同时安装多个 JDK ,于是会有多个 javac 并存。
  • JRE 里没有 javac,所以纯 JRE 只能运行、不能编译。

二、Java 的两步:编译与运行

Java 是「先编译、再运行」的语言,两步用不同的工具:

复制代码
你写的            编译器              产物                运行
Hello.java  ──javac──►  Hello.class  ──java──►  程序运行
(源代码)              (字节码)            (JVM 执行)
  1. 编译javac Hello.java → 生成 Hello.class.class 里是字节码(bytecode),一种 JVM 才能看懂的中间格式,既不是源码也不是机器码。
  2. 运行java Hello → JVM 加载 .class,把字节码翻译成机器码执行。

字节码带来「一次编译,到处运行」:同一个 .class 在 Windows / Mac / Linux 的 JVM 上都能跑。


三、核心:字节码版本与 JVM 向下兼容

这是理解所有版本问题的关键。每个 .class 文件头部都写着一个「字节码版本号」,标明它是哪个 Java 版本编出来的、需要至少哪个版本的 JVM 才能运行:

Java 版本 class 字节码版本号
Java 7 51
Java 8 52
Java 11 55
Java 17 61
Java 21 65

唯一规则:运行时 JVM 的版本必须 ≥ class 的字节码版本。

  • 用 JDK 17 编出来的 class(61)→ 放到 JDK 8 的 JVM(最高认 52)→ ❌ 报 UnsupportedClassVersionError
  • 用 JDK 8 编出来的 class(52)→ 放到 JDK 17 的 JVM → ✅ 正常运行。

一句话总结:JVM 向下兼容,不向上兼容(高版本 JVM 能跑低版本字节码,反之不行)。

由此还能推出一条编译器铁律:javac 只能产出「≤ 自己版本」的字节码,不能编出比自己更高的版本 。让 JDK 7 的 javac 编出 Java 11,逻辑上不可能。


四、控制编译版本:source/target 与 release

现实中常出现「我机器装 JDK 17,但项目要发布到 JDK 8 的服务器」这种错配场景。javac 提供参数来控制「编出什么版本」,这也是 pom.xml 里版本配置的来源。

4.1 老参数:-source-target

bash 复制代码
javac -source 8 -target 8 Hello.java
  • -source 8 :按 Java 8 的语法规则检查源码。用了 Java 9+ 的新语法 → 编译报错。
  • -target 8 :生成 Java 8 版本号(52)的字节码

漏洞(交叉编译陷阱) :这俩管不住「你用了哪些 API」。用 JDK 17 的 javac-source 8 -target 8 时,编译器手里拿的仍是 JDK 17 的类库 。于是你写了 List.of()(Java 9 才有的方法):

  • -source 8 不报错(语法没问题);
  • 编译通过,class 也是版本 52;
  • 但拿到真正的 JDK 8 上运行 → ❌ NoSuchMethodError(JDK 8 的 List 没有 of())。

4.2 新参数:--release(JDK 9 引入,推荐)

bash 复制代码
javac --release 8 Hello.java

--release 8 一次性锁死三样东西

  1. 语法按 Java 8;
  2. 字节码版本按 52;
  3. API 也限定为 Java 8 里真实存在的 ------再写 List.of(),编译阶段就直接报错。

4.3 对比

配置 管语法 管字节码版本 管 API 范围
source / target(老) ❌(用当前 JDK 的 API,有陷阱)
release(JDK 9+) ✅(最安全)

结论 :只要 JDK ≥ 9,优先用 release 取代 source + target

4.4 这些配置是「强制」的

pom 里的版本配置不是参考,而是会被翻译成真实的 javac 参数强制执行:语法不符直接报错、字节码严格按指定版本产出。若要求的版本超过编译器自身能力(如 JDK 8 配 release 17),不会降级将就,而是直接编译失败


五、Maven 的本质:一个调用工具链的 Java 进程

把前面的底层知识铺好后,Maven 就很好理解了。

5.1 Maven 自己是个 Java 程序

Maven 本身是一个 Java 进程,必须跑在某个 JVM 上。 它自己不编译、不打包,而是委托给底层的编译器和打包逻辑------它的角色是「指挥官 / 编排者」,不是「编译器」。

5.2 它如何「调用」编译

一个容易误解的细节:Maven 默认不是 fork 一个外部 javac 命令行进程 ,而是通过 JDK 内置的编译器 API(javax.tools / JavaCompiler 来编译,这个编译器就在运行 Maven 的那个 JDK 里。只有显式配置 <fork>true</fork> 时,才会真的启动一个独立的 javac 外部进程。

打包同理:maven-jar-plugin 默认用 Java 代码(基于 JDK 类库)完成,而不是去 fork 命令行 jar

但无论是「调 API」还是「fork 进程」,用的编译器/工具版本都来自运行 Maven 的那个 JDK。 所以「用什么版本的 JDK 跑 Maven,就用什么版本的编译器」这个结论始终成立。

5.3 运行 Maven 要用 JDK,不是 JRE

因为编译需要 javac,而 javac 只在 JDK 里。所以编译类的 Maven 任务必须用完整 JDK 来跑 。IntelliJ 设置里那个「Runner JRE」选项虽叫 JRE,实际应选一个完整 JDK(这只是历史命名)。

5.4 pom 版本参数 = 传给编译器的参数

pom.xml 里的版本配置,本质就是告诉 Maven「编译时给编译器传什么参数」:

xml 复制代码
<!-- 老写法:相当于 javac -source 8 -target 8 -->
<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

<!-- 新写法(推荐):相当于 javac --release 8 -->
<properties>
    <maven.compiler.release>8</maven.compiler.release>
</properties>

六、版本一致性:为什么 Maven 的 JDK 要和项目对齐

把整条链串起来:

复制代码
运行 Maven 的 JDK
   │ 决定用哪个编译器
   ▼
编译器按 pom 的版本参数编译
   │ 产出
   ▼
带版本号的 .class
   │ 运行
   ▼
某个版本的 JVM 执行(JVM 版本必须 ≥ class 版本)

三种情况:

  • 运行 Maven 的 JDK 太低 (如 JDK 8)但 pom 要 release 17 → 编译器编不出更高版本 → ❌ 编译失败
  • 运行 Maven 的 JDK 太高 (JDK 17)、pom 用老的 source/target 8、运行又在 JDK 8 → ⚠️ 交叉编译陷阱,编译过但运行崩。
  • 三者对齐 (编译 JDK = 目标版本 = 运行 JVM)→ ✅ 最稳,没有版本错配意外。

实践建议:让以下三处指向同一个 JDK 版本------

  1. Maven Runner 的 JDK(IntelliJ → Build Tools → Maven → Runner → JRE);
  2. pom 的编译目标版本releasesource/target);
  3. IDEA 的 Project SDK(Project Structure → Project → SDK)。

七、常见错误速查

报错 / 现象 原因 解决
UnsupportedClassVersionError 用高版本 JDK 编的 class,拿到低版本 JVM 运行 运行用更高 JVM,或编译时降低目标版本
NoSuchMethodError(运行时) 交叉编译陷阱:用了高版本 API,运行在低版本 改用 release 参数锁死 API
release version 17 not supported 编译器版本低于要求的目标版本 用 ≥ 目标版本的 JDK 跑 Maven
invalid flag: --release 用了 JDK 9 之前的 javac,不认识 --release 升级 JDK,或改用 source/target
编译需要 javac 却报找不到 用纯 JRE 跑了 Maven 改用完整 JDK

八、一图总览 & 核心结论

复制代码
┌──────────────────────────────────────────────────────────┐
│ Maven(Java 进程,跑在 JVM 上)                              │
│   不自己编译,委托给 ↓                                       │
├──────────────────────────────────────────────────────────┤
│ 运行 Maven 的 JDK(必须是 JDK,不能是纯 JRE)                 │
│   ├── 编译器(版本 = 该 JDK 版本)                            │
│   │     └─ 受 pom 的 source/target/release 控制产出版本       │
│   └── 打包逻辑(jar)                                        │
└──────────────────────────────────────────────────────────┘
        │ 编译                              │ 运行
   .java ───► .class(带字节码版本号)───────► JVM 执行
                  ▲                              ▲
            版本号必须 ≤ 运行它的 JVM 版本 ───────┘

核心结论(最该记住的 7 条)

  1. javac 是 JDK 的一部分 ,JDK 什么版本,javac 就是什么版本;JRE 里没有 javac
  2. Java 先编译(javac)成字节码 .class,再由 JVM(java)运行
  3. 每个 .class 带字节码版本号,JVM 必须 ≥ 它才能跑------向下兼容,不向上兼容。
  4. javac 只能产出 ≤ 自己版本的字节码,编不出更高版本。
  5. pom 的 source/target/release 是强制的编译参数releasesource/target 多锁了 API,更安全。
  6. Maven 是个 Java 进程,自己不编译/打包 ,默认调用「运行它的那个 JDK」内置的编译器 API(非 fork 命令行 javac),版本随该 JDK 走;运行 Maven 要用 JDK
  7. 让「Maven Runner JDK、pom 目标版本、IDEA Project SDK」三者一致,避免一切版本错配的坑。