在 JUnit Jupiter 上实测测试隔离:默认每个测试方法一个新实例,PER_CLASS 共享一个实例(counter 1、2、3;随机顺序下 6 个种子都有失败);@Nested 每个测试新建外部实例;参数化与重复测试每次调用一个实例;并行执行把 60 万次更新丢失到 319695 且测试仍全部通过;静态状态跨测试类泄漏取决于类的顺序;@TempDir 字段与 static 的区别;被禁用的方法也会构造实例。
用一个 17 行的发票文本程序实测 golden master:时间戳与 UUID 让 6 次运行 0 次与 master 一致;de / tr / ar 语言、时区与 file.encoding 改变 16 / 18 / 16 / 4 / 2 行输出,美国西部时区下连 HashSet 顺序都变了;固定 Locale.ROOT 与 UTC、有序集合与占位符后 6 个环境 0 差异;1 行 bug 在加固版里只有 2 行 diff;update=true 会把 bug 写进 master。
用 Jackson 2.17.2 与本机 HTTP 服务模拟提供者的 8 个版本:手写桩的消费者测试永远通过,默认 ObjectMapper 打到真实服务上 3 个值正确、3 个抛异常、2 个静默解析出错的值;消费者写的 3 条契约在提供者 CI 里拦住 5 个版本;新增字段、改名、字符串、小数、null、删除字段、新枚举值各自的行为。
用 Instrumentation.redefineClasses 逐一验证热替换的限制:只改方法体(含 static)成功;新增/删除方法、新增字段、改签名、改修饰符、改继承关系都抛 UnsupportedOperationException;已有实例立即用新方法体、字段值保持;正在栈上执行的那次调用跑完旧代码。
真实 attach 到另一个 JVM:list() 发现目标、attach 用时 113 ms 并创建 .java_pid 套接字文件、读取系统属性、loadAgent 在目标进程的 Attach Listener 线程里执行 agentmain 并改写目标静态变量;JDK 21 动态加载 agent 会打印 4 行 WARNING;attach 到非 JVM、自己、不存在的 jar 各抛什么异常。
真实录制 3 秒的 JFR:文件 575 KB、185 种事件;jfr print 看 GC 事件(22 条,第一条 1.22 ms)和锁争用事件(240 条,带 previousOwner 与阻塞线程的栈);ExecutionSample 在这台 macOS 上只有 0 条(没有用来找热点);default 与 profile 配置的采样周期与阈值对比。
真实验证 JMX:平台 MBeanServer 有 27 个 MBean(读属性、调 findDeadlockedThreads);注册自己的 Standard MBean 的属性、操作与五种异常;属性变化通知在调用线程里同步发出;远程连接 105 ms、远程 getAttribute 平均 146.69 µs/次。
用 -XX:NativeMemoryTracking=summary 和 jcmd VM.native_memory 分阶段观察:100 MB DirectByteBuffer 让 Other 变成 100.0 MB,300 个线程让 Thread 从 36.3 涨到 642.7 MB(但 RSS 只涨 37 MB),5000 个类让 Metaspace 涨到 10.9 MB;committed 与 RSS 是两个不同的数。
用自己编译的 JNI 函数验证 GCLocker:JDK 21 的 G1 与 Parallel 在 JNI 临界区(600 ms)期间分配线程最长被卡 610.1 / 592.4 ms(无临界区约 1.4 ms);JDK 27 的 G1 不再被卡(0.6 ms),Parallel 仍被卡(604.0 ms)。
验证 Epsilon GC:已用堆只增不减、堆满时 JVM 以退出码 3 直接终止且 OutOfMemoryError 不可捕获;2000 万个短命数组的循环里 Epsilon 161 ms,反而比 Serial 92 ms、Parallel 87 ms 慢;引用存储 0.346 ns,比 G1 的 1.473 ns 便宜。
JDK 21 上实测 Parallel GC:ParallelGCThreads 从 1 到 8,年轻代停顿中位数 69.0 → 22.2 ms;同一个大存活集负载下 Parallel 完成 395,987 个循环、G1 456,530、Serial 196,885(Parallel 没有赢过 G1);默认 GCTimeRatio = 99 的目标是 1% 的 GC 时间,实际 45.1%。
防御性验证 Java 反序列化过滤器:白名单("!*" 拒绝其余)与黑名单的区别;maxdepth、maxarray、maxrefs、maxbytes 各自拦住什么;进程级过滤器只能设置一次、流上的过滤器取代它、自定义过滤器与 merge、rejectUndecidedClass。只用无害的自定义类实验。