服务能跑之后,问题才刚开始
卷二结束的时候,libraryd 已经是一个能启动、能借书、能查指标、能打成镜像的服务。它通过了 33 个测试,能在容器里响应请求,也能在数据库断开时返回 503。从「做一个能用的服务」这个目标看,它已经完成了。
但你在读卷二代码时,可能已经遇到过一些说不清的地方。
LoanCounters 里为什么用 LongAdder 而不是 AtomicLong?synchronized 到底保证了什么,又没保证什么?@Transactional 为什么在类内部自调用时不生效?虚拟线程听起来能开一百万个,为什么连接池还是只能开十个?Class.forName 和 ClassLoader.loadClass 有什么区别?switch 里加一个 case 为什么能让编译器提醒你漏了分支?
这些问题不属于「怎么把服务跑起来」,而属于「这门语言在运行时到底做了什么」。它们不会阻止你交付第一个版本,但会在第二次、第三次改造时反复出现。
《Java 高级编程》处理的就是这一层。
四个层次
本书把「高级」拆成四个可以分别练习的层次,而不是按 API 目录罗列。
第一层是类型系统。 泛型、型变、类型擦除、sealed 层次、record 模式、穷尽 switch。它们的共同目标是把一部分运行时错误提前到编译期。卷二里 Repository 只有 Book 一个实现,看起来不需要泛型;一旦要同时支持 Book、Member、Loan,泛型边界和类型令牌就会决定 API 好不好用。
第二层是运行时。 反射让你在运行时检查类,注解把元数据带到运行时,动态代理让你在不改实现的情况下包一层事务或重试,类加载器决定一个类从哪里来,字节码与 Instrumentation 决定你能不能在不改源码的情况下观察方法调用。这一层是框架的地基:Spring 的 @Transactional、MyBatis 的 Mapper、Mockito 的 mock,拆开看都是这些机制的组合。
第三层是并发。 Java 内存模型规定线程之间什么可见、什么可以重排;原子类和锁建立在这些规则之上;JUC 提供了容器、队列和同步器;虚拟线程改变了「一个请求一个线程」的成本结构,但没有改变连接池、限流和临界区的约束;结构化并发把「一组相关任务」变成一个作用域。这一层的难点不是记住 API,而是知道每条规则在什么条件下成立。
第四层是平台与性能。 JPMS 把依赖边界从「约定」变成「编译器检查」,FFM 让 Java 直接调用本地库而不写 JNI,JMH 让微基准不再靠 System.nanoTime() 猜,JFR 让线上热点可以被采样而不是靠猜。性能工程的底线是:先测量,再改动,再回归。
四层不是严格递进的关系,但本书按这个顺序组织,因为后一层的问题常常需要前一层的语言来表达。比如结构化并发的返回值要靠 sealed 类型描述,动态代理的事务边界要靠泛型接口约束。
与卷四的分工
卷四《Java 虚拟机原理与性能调优》讲 JVM 内部:类加载的细节、运行时数据区、对象布局、解释器与 JIT、GC 算法、GC 日志、内存问题诊断、启动加速。那是「虚拟机怎么实现这门语言」。
卷三讲「语言和标准库给开发者提供了什么机制,怎么正确使用」。卷三第 9 章会用到 JFR、JMH 和 JVM 参数,但不会展开 GC 算法和 JIT 编译策略;遇到需要深入虚拟机的部分,会明确指向卷四。
同样,本书不重复 Spring Boot 三部曲的内容。Spring 的注解怎么用,那一套书写得更完整;本书关心的是注解背后的反射、代理和类加载。
JDK 25 这个时点
本书主线是 JDK 25 LTS(Temurin 25.0.5)。选它不是因为版本号新,而是因为几个长期存在的「Java 就是这样」的结论在这个版本前后发生了变化,如果继续照旧写,会教错人。
synchronized不再 pin 虚拟线程。 JDK 24 的 JEP 491 解决了这个问题。旧文章里「虚拟线程里不要用 synchronized,要换 ReentrantLock」的建议,在 JDK 25 上已经不成立。本书第 6 章会把这段历史讲清楚,而不是简单重复旧结论。- Scoped Values 正式。 JDK 25 的 JEP 506 让
ScopedValue成为正式 API,它提供了比ThreadLocal更适合虚拟线程的上下文传递方式。第 6.3 节会对比两者。 - StructuredTaskScope 仍是预览。 JDK 25 是第五次预览(JEP 505),API 形态与 JDK 21 的早期预览不同,编译和运行都需要
--enable-preview。本书会明确标注,不会把它当成稳定 API。 - FFM 的 native access 开始收紧。
java.lang.foreign在 JDK 22 正式,但 JDK 25 对 restricted methods 默认告警,要求--enable-native-access。这个趋势会影响所有调用本地库的项目,第 8 章会给出真实告警原文。 - primitive patterns 仍是预览。 JDK 25 的 JEP 507 还在第三轮预览,本书不会把它当作可用特性。
这些变化说明一件事:Java 的「高级」不是一套静止的知识,而是一组随版本移动的边界。 本书会尽量把「哪个版本成立」写清楚,而不是给一个脱离版本的结论。
三条阅读路径
路径一:机制补课。 如果你已经写过几年 Java,但反射、类加载、JMM 这些词一直停留在「听说过」,按第 1–9 章顺序读。每章的三节是「机制 → 工具 → 项目落地」的结构,可以只读前两节,再挑感兴趣的落地节。
路径二:改造 libraryd。 如果你想看这些机制怎么进入一个真实服务,从第 1 章的 Repository<T, ID> 开始,跟着每节的「在 libraryd 里落地」改代码。第 3 章的插件加载器、第 6 章的虚拟线程聚合、第 8 章的模块化与 FFM,都是可以直接运行的改造。
路径三:排障与面试。 如果你只需要解决具体问题,先看目录里的小节标题,再跳到对应章节。第 4 章的可见性、第 5 章的并发容器、第 9 章的 JMH 与 JFR,都是高频问题;附录 A–D 提供速查表、模板、版本矩阵和诊断命令。
三条路径都不要求先读完卷二,但需要你能看懂卷二里的 libraryd 类型:Book、Member、Loan、ErrorCode、LibraryException。这些类型的定义在每章第一次出现时会简要重复。
本书不写什么
- 不写 JVM 内部实现细节:对象头布局、GC 算法、C1/C2 编译策略属于卷四。
- 不写框架用法:Spring Boot 的自动配置、Javalin 的全部路由能力属于各自的专题。
- 不写「并发编程入门」:假定你知道线程、
Runnable、ExecutorService的基本用法。 - 不承诺性能数字:所有基准都给出机器口径和复现命令,换机器结果会变。
- 不把预览特性包装成正式特性:用到预览 API 的地方都会标注
--enable-preview与版本。
验证方式
本书的代码分三类处理。
第一类是完整类,全部用 JDK 25 编译并运行,输出以「本机输出」标注,环境是 macOS Apple Silicon、Temurin 25.0.5、Maven 3.9.12。第二类是片段,按上下文补全后编译,正文会说明补全了什么。第三类是反例,允许编译失败或运行异常,正文会明确标注「反例」并解释失败原因。
并发实验有不确定性。某次运行没有复现可见性问题,不代表问题不存在;本书会如实写出「本次未复现」以及调度层面的原因,而不是伪造一个漂亮的结果。性能数字同理:给出命令、参数和机器口径,读者应当在自己的环境里重跑。
从哪开始
如果你还没有决定,先读第 1 章。它从卷二那个只有一个实现的 BookRepository 开始,把接口抽成 Repository<T, ID>,然后立刻遇到泛型擦除、型变和类型令牌三个绕不开的问题。这三个问题会一直贯穿到第 7 章的模式匹配。
读完第 1 章,你会知道这本书的写法:不是「这个语法叫什么」,而是「这个机制解决了哪个具体问题,代价是什么,什么时候不该用」。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。