这本书写给谁
本书写给已经能写 Java,但对「运行时到底发生了什么」缺少系统认识的开发者。典型情况有三类。
第一类是做业务开发两三年,能熟练使用集合、Stream、Spring 注解,但遇到 ClassNotFoundException、NoSuchMethodError、IllegalMonitorStateException 时只能搜索报错。第二类是从其他语言转来,知道协程、泛型、模块,但不确定 Java 的对应物在哪个版本、有什么限制。第三类是要做技术选型或面试准备,需要把零散的知识点连成一条可解释的链路。
不适合两类读者:完全没写过 Java 的初学者,以及只想找「XX 面试题标准答案」的读者。前者请先读卷一《Java 编程入门》,后者会发现本书的结论都带条件和版本。
前置知识
假定你已经掌握:
- Java 基本语法、类与接口、异常、集合、Stream、Lambda。
- 多线程的基本概念:
Thread、Runnable、ExecutorService、synchronized的粗略用法。 - Maven 的基本命令:
mvn compile、mvn test、mvn package。 - 能读懂
record、sealed、switch表达式;如果读不懂,第 7 章会补,但前面几章会用到record。
不要求你读过卷二,但本书的例子大量引用 libraryd 的类型。第一次出现时会给出定义,完整的模块结构见下一节。
环境清单
本书的代码在下面这套环境里编译和运行。版本不是「建议」,而是文中输出的来源;版本不同,输出可能不同。
$ java -version
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 -version
javac 25.0.5
$ mvn -version
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
$ docker --version
Docker version 29.7.2, build a7dcaa6fdb
$ clang --version
Apple clang version 16.0.0 (clang-1600.0.26.6)
Target: arm64-apple-darwin25.3.0
机器是 Apple M1 Pro,macOS 26.3。第 9 章的 JMH 数字来自这台机器,换机器后数值会变,但相对关系通常保留。
JDK 路径按你的安装位置调整。本书统一用:
export JAVA_HOME=~/.jdks/jdk-25.0.5+8/Contents/Home
export PATH="$JAVA_HOME/bin:$PATH"
如果 java -version 显示的不是 25,先确认 JAVA_HOME,再看 mvn -version 里的 runtime。Maven 会用 JAVA_HOME 指向的 JDK,而不是 PATH 里第一个 java。
项目结构
卷三继续使用卷二的 libraryd,保留五个模块,新增两个只在开发和验证时使用的模块:
libraryd/
├── pom.xml # com.plume:libraryd-parent:1.0.0-SNAPSHOT
├── libraryd-domain/ # 领域类型、泛型仓储、sealed 结果、模式匹配
├── libraryd-persistence/ # JDBC 仓储、动态代理事务、并发事件流水线
├── libraryd-web/ # Javalin 7、虚拟线程、结构化并发聚合
├── libraryd-app/ # AppMain、装配、module-info、FFM 调用
├── libraryd-integration-tests/ # Testcontainers 集成测试
├── libraryd-agent/ # 第 3 章新增:Premain-Class 计时 Agent
└── libraryd-benchmarks/ # 第 9 章新增:JMH,不随镜像发布
依赖方向不变:domain 不依赖任何模块;persistence、web 依赖 domain;app 依赖全部;integration-tests 依赖 app。agent 只依赖 Byte Buddy 和 JDK,benchmarks 依赖 domain 与 persistence。
包根是 com.plume.library。本书不会为了演示某个机制而改包名,也不会新建一套平行的示例工程;所有代码都放回 libraryd 的对应模块里。
全书结构
九章分四个部分,每章三节:
| 部分 | 章 | 主题 | 落点 |
|---|---|---|---|
| 一、类型 | 1 | 类型系统进阶 | Repository<T, ID>、型变、类型令牌 |
| 2 | 反射、注解与动态代理 | 反射工具、@Audit、事务代理 | |
| 3 | 类加载、字节码与 Instrumentation | 插件加载器、javap、计时 Agent | |
| 二、并发 | 4 | JMM 与并发原语 | 可见性、原子类、锁 |
| 5 | JUC 与并发容器 | 并发容器、事件流水线、同步器 | |
| 6 | 虚拟线程与结构化并发 | 虚拟线程、StructuredTaskScope、ScopedValue | |
| 三、语言与平台 | 7 | 现代语法与模式匹配 | LoanState、穷尽 switch、守卫 |
| 8 | JPMS、FFM 与原生互操作 | module-info、jlink、本地库调用 | |
| 四、性能 | 9 | 性能工程 | JMH、JFR、优化决策 |
第 1–3 章是「编译期与运行时能看到什么」,第 4–6 章是「多个线程同时做一件事时规则是什么」,第 7 章把前两类问题用类型表达出来,第 8 章处理 Java 代码与外部世界的边界,第 9 章处理「怎么知道改动真的有效」。
代码与输出约定
完整类。 能独立编译的类会给完整源码,包括 package、import 和 main(如果可运行)。你可以直接复制到文件里执行。
片段。 只展示关键部分的代码用省略号或注释说明上下文,正文会写清「这段代码位于哪个方法里」。片段不保证单独编译。
反例。 标注「反例」的代码块故意保留错误,可能编译失败或抛异常。它们用来展示边界,不要直接用于生产。
真实输出。 放在 text 块里,并标注「本机输出」。如果为了排版删除了无关行,会写明「已省略」。没有真实运行过的输出标为「示意输出」。
命令。 bash 块里的命令按顺序执行。涉及端口的命令会给出可替换的端口号;涉及临时目录的用 /tmp/vol3-*,不污染仓库。
版本标注。 涉及预览 API 的代码块会写 --enable-preview;涉及本地访问的会写 --enable-native-access。如果某个结论只适用于特定 JDK 版本,正文会直接写出范围。
三种验证层次
本书的代码按可验证程度分三层,每节会尽量做到前两层。
第一层:编译。 所有完整类在 JDK 25 下通过 javac --release 25。用到预览 API 的类通过 javac --release 25 --enable-preview。读者可以用同一条命令复现。
第二层:运行。 能独立运行的程序会给出 java 命令和真实输出。并发、类加载、Agent、FFM、JMH、JFR 的实验都属于这一层。运行结果依赖机器和调度,正文会写清哪些是确定性输出,哪些是本次运行的观测值。
第三层:项目集成。 涉及 libraryd 多模块的改动,用 mvn -q -DskipTests package 验证编译与打包;涉及数据库的用 Testcontainers 或本机 PostgreSQL。第三层不会在每节都跑,但每章至少有一个可运行的落地点。
版本不一致时怎么排查
如果你在自己的环境里得到不同结果,按下面顺序排查,不要先改代码。
- JDK 版本。
java -version与javac -version是否都是 25。--release 25只能保证字节码目标,不能替代运行时的 JDK 25。 - 预览开关。
StructuredTaskScope、primitive patterns 需要--enable-preview,编译和运行都要加。只加编译不加运行会得到UnsupportedClassVersionError或预览特性不匹配的报错。 - 模块路径。 第 8 章的
jlink、module-info对--module-path敏感。类路径和模块路径混用时,先看jdeps的输出。 - 本地库。 FFM 示例需要
libisbn.dylib(macOS)或libisbn.so(Linux)在java.library.path或显式路径下。找不到库时SymbolLookup返回空,不要误判为 API 用错。 - 并发调度。 可见性和重排序实验可能在某些运行中「看起来正常」。先确认线程数、迭代次数和
-XX参数与正文一致,再判断结论。 - 依赖版本。 JMH、Byte Buddy、ASM 的版本写在父 POM 的
dependencyManagement里。子模块不要单独写版本,否则会出现「代码一样、行为不同」。
关于示例的取舍
本书刻意不引入 Lombok、MapStruct、Guava 等简化库。原因不是它们不好,而是它们会遮住本书要讲的机制:Lombok 用注解处理器生成代码,MapStruct 用注解处理器生成映射,Guava 的缓存和并发工具封装了大量细节。读卷三时,你需要看到 Method、InvocationHandler、ClassLoader、VarHandle 这些原始形态。
同样,本书不把每个实验都做成完整框架。插件加载器只加载一个 ISBN 校验器,Agent 只计时一个方法,JMH 只测两个实现。例子小,才能把变量控制住;真实项目里的复杂度,应该由读者在自己的场景里叠加。
与站内其他内容的关系
- 卷一《Java 编程入门》补齐语言基础。
- 卷二《Java 编程实战》补齐工程化与服务端能力。
- 卷四《Java 虚拟机原理与性能调优》补齐 JVM 内部与调优。
- Spring Boot 三部曲补齐框架层;本书第 2 章的反射与代理、第 3 章的类加载,是理解 Spring 容器的基础。
如果时间有限,优先读第 1、4、6、9 章:它们分别对应类型、内存模型、虚拟线程和性能测量,是实际项目里最容易出错、也最容易被旧资料误导的部分。
开始之前
先把环境跑通:
export JAVA_HOME=~/.jdks/jdk-25.0.5+8/Contents/Home
export PATH="$JAVA_HOME/bin:$PATH"
java -version
javac -version
mvn -version
三条命令都显示 25.0.5 之后,再打开第 1 章。如果你用的是其他 25.x 补丁版本,绝大多数示例仍然成立;涉及具体输出的地方,以你本机为准。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。