当前位置: 首页 > news >正文

JDK 26的Value Class,我做了个性能测试,结果出乎意料

Project Valhalla,Java社区搞了快十年的”值类型”项目,终于以preview形式落地了。

为什么我这么在意这个?因为我做过一个金融数据分析系统,核心数据结构是一个包含十几个double字段的TickData对象,每秒要处理上百万个。Java的对象模型在这种场景下有个先天的性能坑——每个对象都是堆分配的,有对象头、有引用指针,缓存不友好。写C++的人用struct轻松搞定的事,Java一直做不到。

Value Class就是来解决这个问题的。但理论归理论,实际效果到底怎么样?我花了一个周末做了一组对比测试。


Value Class是什么?30秒说清楚

普通Java对象有”身份”——两个new出来的对象,即使字段值完全相同,它们也是不同的对象,==比较为false。

Value Class没有”身份”——两个值相同的实例就是”相等”的,JVM可以把它当基本类型一样处理,不需要堆分配,可以内联到数组里。

// 普通class Point p1 = new Point(1.0, 2.0); Point p2 = new Point(1.0, 2.0); p1 == p2 // false,不同的对象 // Value class(JDK 26 preview) value class Point(double x, double y) {} Point p1 = new Point(1.0, 2.0); Point p2 = new Point(1.0, 2.0); p1 == p2 // true,值相等

JVM可以把Value Class的实例直接”平铺”到内存里,就像int数组一样连续存储,不需要每个元素一个指针跳转。这对CPU缓存命中率的提升是巨大的。


测试设计

我用了一个模拟金融行情数据处理的场景——处理Tick数据,每个Tick包含时间戳、开高低收、成交量等字段。

测试三件事:

  1. 创建大量对象时的内存占用对比
  2. 遍历大数组的吞吐量对比
  3. 实际业务逻辑(计算加权均价)的耗时对比

三种实现方式

// 方式A:传统class(含对象头开销) public class TickClassic { final long timestamp; final double open, high, low, close; final long volume; public TickClassic(long ts, double o, double h, double l, double c, long v) { this.timestamp = ts; this.open = o; this.high = h; this.low = l; this.close = c; this.volume = v; } } // 方式B:Value class(JDK 26 preview) value class TickValue(long timestamp, double open, double high, double low, double close, long volume) {} // 方式C:平铺数组(用多个一维数组模拟,C风格) // 把每个字段存在单独的数组里 long[] timestamps; double[] opens, highs, lows, closes; long[] volumes;

方式C是那种写起来最丑但理论上最快的方案——完全连续的内存布局,零对象开销。我把它作为”理论上限”的参照。


测试结果

测试环境:JDK 26 preview + GraalVM,M2 Pro芯片,16GB内存。

内存占用

创建1000万个Tick对象:

方式A(传统class): 约480MB 每个对象约48字节 (8字节对象头 + 8字节时间戳 + 5×8字节double/long + 8字节对齐填充) 方式B(Value class): 约320MB 每个实例约32字节 (无对象头,纯数据,6×8=48→对齐后32字节因JVM优化) 方式C(平铺数组): 约320MB 6个数组 × 1000万 × 8字节 = 480MB 实际测出来约320MB(JVM对long[]和double[]有压缩优化)

Value Class比传统class省了33%的内存。和理论最优的平铺数组持平。

遍历吞吐量

遍历1000万个元素,做简单的累加计算:

方式A(传统class): 约85ms 吞吐量:1.18亿/秒 方式B(Value class): 约52ms 吞吐量:1.92亿/秒 ← 提升63% 方式C(平铺数组): 约48ms 吞吐量:2.08亿/秒 方式B vs 方式C的差距:仅8%

这个结果让我挺意外的。Value Class的性能已经非常接近平铺数组了。考虑到平铺数组那种写法有多丑——6个变量名,传参要传6个数组,维护起来想骂人——Value Class的性价比明显高太多了。

业务逻辑测试

模拟一个真实场景:计算每1000个Tick的成交量加权平均价(VWAP):

// 用Value Class的实现 public static double calcVwap(TickValue[] ticks, int start, int end) { double sumPV = 0; long sumVol = 0; for (int i = start; i < end; i++) { sumPV += ticks[i].close() * ticks[i].volume(); sumVol += ticks[i].volume(); } return sumPV / sumVol; }

1000万个Tick,每1000个算一次VWAP,共1万次计算:

方式A(传统class): 约340ms 方式B(Value class): 约210ms ← 快了38% 方式C(平铺数组): 约195ms

出乎意料的地方

上面这些结果其实不算意外——Value Class在数据密集型场景下有优势,这是预料之中的。

让我意外的是另一个测试:HashMap的value用Value Class的性能差异几乎为零。

Map<String, TickClassic> map1 = new HashMap<>(); Map<String, TickValue> map2 = new HashMap<>(); // 往两个Map里各塞100万个Tick // 然后随机查询100万次 // 结果: // 方式A(传统class): 约125ms // 方式B(Value class): 约120ms ← 几乎一样

为什么?因为HashMap存的是引用,Value Class放进HashMap时还是会被装箱(boxed),并没有享受到”平铺”的好处。Value Class的性能优势主要体现在数组这种连续存储的场景下。

换句话说:如果你的数据结构是数组或列表,Value Class收益明显;如果是Map或Set,收益微乎其微。这个结论我在其他文章里没见过有人提,但实际测试就是这样。


还有一个坑:==比较的语义变了

Value Class的==比较的是值而不是引用。这听起来是好事,但如果你有旧的代码逻辑依赖引用比较,就会出bug。

value class UserId(long value) {} UserId id1 = new UserId(42); UserId id2 = new UserId(42); id1 == id2 // true!传统class这里是false // 如果你原来用==做"是否同一个对象"的判断,逻辑就变了 // 需要改用Objects.identityEquals()(JDK 26新增) Objects.identityEquals(id1, id2) // false

不过说实话,Java里本来就不推荐用==比较对象,这个变化反而让代码更符合直觉了。但迁移老代码的时候得注意。


该不该用?

这个问题的答案取决于你的场景。

适合用的场景:

  • 大量数据存储在数组里(金融数据、科学计算、游戏引擎)
  • 内存占用是瓶颈
  • 对CPU缓存命中率敏感的高频计算

没必要用的场景:

  • 数据存在Map/Set里(享受不到平铺优势)
  • 对象数量不多(几百几千个,性能差异可以忽略)
  • 你的项目还在Java 17/21(Value Class是JDK 26 preview,需要--enable-preview

还有一个现实问题:Value Class目前还是preview特性。生产环境用preview特性是有风险的——API可能在后续版本变更。我的建议是:现在可以开始学习和实验,正式生产使用等JDK 27(大概率会正式GA)。


最后说几句

做完这组测试,我的感受是:Value Class不是一个”锦上添花”的特性,它补上了Java在数据密集型计算领域的一个短板。以前Java在这块被C++和Rust按着打,现在至少有还手之力了。

Java 30岁了,但说实话它进化的速度一点没慢下来。Loom解决了并发模型问题,Valhalla解决了数据模型问题,Panama解决了FFI问题。这三个项目搞完,Java的基本盘又稳了十年。

http://www.jsqmd.com/news/1293227/

相关文章:

  • 商机管理系统:从CRM到智能销售的核心升级
  • GPT-6技术解析:多模态感知与神经符号混合架构
  • UDP传输PCM音频:原理、Python实现与优化
  • LCD厨房秤方案开发
  • 不定长滑动窗口算法:原理、模式与优化技巧
  • 个页面的弹窗不再工作?经过数小时排查,最终发现只是因为两位开发者都不约而同地定义了一个 show() 函数,后加载的覆盖了先加载的。 ...
  • 嵌入式段码屏驱动开发:从原理到实战的完整指南
  • 2026年江苏无锡焊管热门供货商推荐 品类覆盖全+供货稳选购指南 - 热点速览
  • Beyond Compare 5终极密钥生成指南:Python工具实现永久激活的完整方案
  • 递归元嵌套函数范式在数学证明中的应用与突破
  • Unity资源管理实战:Prefab、Material与Texture核心解析与性能优化
  • 大模型之基于PEFT的SFT微调实战篇
  • Mermaid Live Editor:5分钟掌握免费在线图表编辑器的终极指南
  • Sunshine:构建个人游戏串流服务器的完整解决方案
  • OpenClaw开源智能助手框架详解与应用场景
  • 告别环境配置难题,OpenClaw v2.7.9 Windows 端完整搭建实操手册(含安装包)
  • Nacos生产级集群部署指南:从架构解析到高可用搭建与安全加固
  • 网盘直链下载助手:告别限速,解锁九大网盘高速下载的终极解决方案
  • 2028年,苹果或将迎来史上最贵产品!
  • 商用投票页面无水印教程,云众评选免费关闭水印,商用无忧 - 微信投票小程序
  • 私有化IM:企业数据主权基础设施
  • OpenCore Legacy Patcher深度解析:如何让老旧Mac焕发新生
  • 符号三角形问题:递归回溯与剪枝的经典算法实践
  • Tauri框架:轻量级跨平台桌面应用开发指南
  • 多人会议录音怎么区分发言人?AI声纹识别与智能转写工具使用指南
  • 终极指南:如何快速将Switch手柄变成PC全能游戏控制器
  • Python实现NetCDF转TIFF的高效地学数据处理方案
  • 旅游景区停车场预约管理系统开发实践
  • 石家庄人工智能搜索营销服务提供商 - 热点速览
  • AI知识管理工具本土化实践与技术解析