jmh 测密封类+模式匹配性能需控制变量、暴露jit行为,对比if-else、visitor、sealed switch三方案,配置预热/测量轮次、zgc、动态代码数据等参数,采集吞吐量、延迟、内存分配、分支未命中率,识别拐点与退化边界。

直接用 JMH 测密封类 + 模式匹配的性能边际,核心不是“能不能快”,而是“在什么条件下快多少、为什么快、边界在哪”。关键要控制变量、暴露 JIT 行为、测出真实收益点。
明确测试目标与对比基线
不能只跑一个 sealed + switch 就下结论。必须固定输入规模、类型分布、分支数量,横向对比三类典型实现:
- 传统 if-else + instanceof + 强制转型(最常见但最慢)
- 经典 Visitor 模式(双分派开销明显,适合复杂行为但调度重)
- sealed interface + exhaustive pattern-switch(目标方案,含记录模式与守卫)
每组测试需使用完全相同的表达式树结构(如 Lit/Add/Mul 层次),确保差异仅来自分发机制本身。
配置JMH参数以捕获真实JIT优化效果
JMH 默认配置会掩盖密封类的关键优势。必须显式启用以下选项:
- 预热轮次 ≥ 10,测量轮次 ≥ 10,fork=3:确保 C2 编译器完成全部优化(包括 tableswitch 生成和死代码消除)
-
添加 JVM 参数:
-XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+EnableDynamicCodeData(Java 25 必需,否则密封类运行时优化不生效) -
禁用 Profile-Guided Optimization 干扰:加
-XX:-UseBranchPrediction可观察分支预测失效时的退化表现,反向验证守卫条件的内联效果
特别注意:若测试中包含 default -> throw 分支,JIT 在密封类穷尽性保障下会彻底删除该分支字节码——这正是 47% 提升的主因之一,必须通过 -XX:+PrintOptoAssembly 确认是否真被裁剪。
设计多维度指标,识别性能拐点
单一 ops/s 数值无法反映边际变化。应同时采集:
- 吞吐量(Throughput):看整体调度能力,适用于高并发任务分发场景
- 平均执行时间(AverageTime):定位单次 dispatch 的延迟压缩效果,单位设为 ns/op
- 内存分配率(GC.alloc.rate.norm):密封类 + 记录模式可避免装箱与中间对象,Java 19 实测内存占用降低 42%
- 分支未命中率(perf stats -e branch-misses):配合 Linux perf 工具,验证跳转表是否真正替代了链式判断
例如当 case 分支从 3 个增至 8 个时,if-else 吞吐量通常线性下降,而 sealed switch 基本持平——这个拐点就是“边际稳定区”,也是你决定是否迁移的依据。
模拟真实退化场景,验证鲁棒性边界
密封类不是银弹。需主动测试其性能临界点:
- 在
permits列表中故意漏写一个子类(触发IncompatibleClassChangeError),观察启动阶段校验耗时增幅 - 跨模块使用时移除
permits module M声明,测运行时反射访问密封类的开销(Java 25 新增SealedClassVerifier验证成本) - 在 switch 中混用非密封子类(如
case Object o ->),破坏穷尽性,看 JIT 是否仍能做常量折叠
这些测试不为“让它变慢”,而是确认:你的系统在哪些配置失误或演进路径下会失去那 46.9% 的收益——这才是真正的边际表现。











