在自己的测试 XML 与本地服务上验证 JAXP:JDK 21 默认的 DOM、SAX、StAX、Transformer 都会解析本地文件实体,DOM 还会请求远程 DTD;各种安全开关里有的拒绝(抛错)、有的悄悄忽略;嵌套实体默认被 64000 次展开限制挡住,关闭 secure processing 后 10^6 级别的展开耗时 755 ms。
用 javax.tools 在程序里编译源码:内存编译首次 150 ms、预热后约 8 ms;诊断带行列号与诊断码;-Xlint 与 -Werror;--release 同时限制语法与 API;jlink 出的运行时缺 jdk.compiler 时 getSystemJavaCompiler() 为 null。
用 com.sun.source 的 Tree API 分析一段 24 行的 Java 源码:184 个语法树节点、圈复杂度、方法调用及源码位置;parse() 只报语法错误,analyze() 之后才有类型信息与语义错误;24 行代码 parse 2.4 ms、analyze 22.8 ms。
写一个只有几十行的自定义 doclet 遍历 shop 包:12 个成员、5 个没文档注释;-private、自定义选项与错误选项;标准 doclet 生成 36 个文件并被 -Xdoclint 报 9 条警告;注释继承在标准 doclet 里自动展开、自己的 doclet 要自己处理。
验证 java.util.logging:logger 级别与 handler 级别两道关、父链传播;被过滤的 fine 调用拼接 21.9 ns、Supplier 0.6 ns;FileHandler 的 limit/count 轮转;没有强引用的 logger 在 GC 后级别丢失;System.Logger 默认包装 JUL。
验证 java.util.zip:zlib、raw deflate、gzip 的头尾分别 6 与 18 字节;level 9 比 6 小约 5%、耗时约 5 倍;随机数据压不动;截断、损坏、拼接与垃圾字节的异常;256 MiB 全零只有 254 KiB 的解压炸弹;syncFlush。
用 javap 与反射看 javac 生成的桥接方法:Box 里源码写了 3 个方法,class 文件里多出 3 个 ACC_BRIDGE|ACC_SYNTHETIC 方法;桥接方法只做 checkcast 后转调真实方法,注解会被复制,栈帧行号指向类声明行;包私有父类的 public 方法还会有可见性桥接。
验证 record 的序列化:反序列化调用 canonical 构造器,因此构造器里的校验不会被篡改的流绕过(普通类会读出 age=-5,record 抛 InvalidObjectException);增加/删除/改名组件兼容,改类型不兼容;serialVersionUID 不参与比较;writeObject/readObject 被忽略,writeReplace/readResolve 有效。
用反射、javap 与故意制造的错误看 sealed:getPermittedSubclasses() 读 class 文件里的 PermittedSubclasses 属性;javac 与 JVM 两层检查(IncompatibleClassChangeError);只重编译一部分后旧 switch 遇到新子类抛 MatchException。
验证 Java 21 的模式 switch:没有 case null 时 null 抛 NPE;守卫按顺序求值、类型不符的分支不执行守卫;record 模式嵌套解构,访问器异常包装成 MatchException;被支配分支与不穷尽的编译错误;字节码是 invokedynamic typeSwitch。
写一个真实的注解处理器(生成 Builder 类)并用 javac 运行:Filer 生成源文件,处理分 3 轮(第 2 轮处理新生成的源文件),Messager 的 ERROR/WARNING 带元素位置,-Werror、-proc:none/only/full 与 classpath 上的 services 文件在 JDK 21 的表现。
用 jar 工具与 Maven 打多版本 JAR 并在 JDK 17、21、27 上运行:同一个 jar 在不同 JDK 上取不同版本的实现;没有 Multi-Release: true 或 -Djdk.util.jar.enableMultiRelease=false 时只用 base;jar 工具会拒绝公共 API 不一致的版本类。