开发者生态
evening
值类仍然需要编译器的支持
摘要
This post discusses preview features in JDK 28 JEP 401 , a major Valhalla milestone, has been integrated as a preview feature in JDK 28. This is very exciting, as value classes increase both our abili...
the
that
value
JVM
classes
you
and
for
This
representation
2026-08-26
1 阅读
约7分钟阅读
lichtenberger
字号:
这篇文章讨论了 JDK 28 中的预览功能 JEP 401 是 Valhalla 的一个重要里程碑,已作为预览功能集成到 JDK 28 中。这是非常令人兴奋的,因为值类提高了我们与其他人交流程序语义的能力以及 JVM 可用的优化机会。然而,我在网上看到人们基本上采取的是“重视所有课程!”对此的方法。我担心的是,人们相信普通课程会给你的表现设定一个底线,而价值课程会尽最大努力让你高于这个底线,但永远不会让你低于这个底线。不幸的是,事实并非如此。一个善意的程序可能会将 JVM 置于这样一种情况:扁平化表示对于某些方法更快,而引用表示对于其他方法更快。当这些方法交互时,JVM 被迫在两种表示形式之间进行转换。我希望值类不仅仅是魔法,所以今天我将向您展示 JVM 目前的功能以及它的局限性。我希望通过这个,您将有一些背景来推理您(或您的人工智能代理)编写的代码。值类的主要优化优势是我们放弃了身份。这使得 JVM 可以自由地为特定情况选择合适的表示形式。在不需要身份的情况下,运行时可以更容易地展平值(避免指针追逐),通过在寄存器或堆栈上独立地表示它们的组件来缩放它们。对于价值对象本身,逃逸分析变得微不足道:没有任何身份的逃逸必须被证明是不可观察的。我们将研究三个示例:平坦存储的大型最终值、无需分配而编译的直接值转换以及需要具体化的通用虚拟调用。不变性可以实现扁平化 JEP 539,JVM 中的严格字段初始化,让 JVM 依赖于在其封闭对象变得可观察之前已初始化的最终字段。由于此类字段以后无法更新,因此 JVM 可以使用非原子扁平布局,而不会面临分配中断的风险。然而,可变字段必须保留无撕裂分配。如果可变字段包含对于原子扁平化更新来说太大的值,则 JVM 必须改用引用布局。严格初始化保证开辟了许多优化的可能性。考虑这个小例子: value record FourLongs (long a, long b, long c, long d) {} record Envelope (FourLongs Payload) {} FourLongs 有 32 字节的有效负载,对于当前 JVM 中的原子扁平化更新来说太大了。但 Envelope.payload 是一个记录组件,因此是一个严格初始化的最终字段:一旦初始化,它就永远不会更新。因此,JVM 可以使用非原子扁平布局自由存储有效负载。在当前的 Valhalla 主版本中,使用 PrintFieldLayout 时字段布局诊断报告以下内容: 类 FourLongs @8 REGULAR 8/8 "a" J @16 REGULAR 8/8 "b" J @24 REGULAR 8/8 "c" J @32 REGULAR 8/8 "d" J @40 NULL_MARKER 1/1 的布局NULLABLE_NON_ATOMIC_FLAT 布局:33/8 类 Envelope @8 FLAT 的布局 33/8“有效负载”LFourLongs; FourLongs NULLABLE_NON_ATOMIC_FLAT 这里我们可以看到 FourLongs 由它的四个组件和一个 1 字节空标记组成,并且它支持可为空的非原子扁平布局。运行时在 Envelope 记录中使用这一事实,并允许 FourLongs 被展平。关键是 Envelope 也是不可变的;如果我们用可变类替换它,布局就必须改变:公共 MutableEnvelope (FourLongs 有效负载) { this .有效负载=有效负载; MutableEnvelope 类的布局 @8 REGULAR 4/4 "payload" LFourLongs;这是为什么?让我们考虑两个线程之间的数据竞争: void thread1 (MutableEnvelope a) { a.有效负载 = 新的 FourLongs(1, 0, 0, 0); } void thread2 (MutableEnvelope a) { a.有效负载 = 新的 FourLongs(0, 1, 0, 0); } void main () 抛出 InterruptedException { MutableEnvelope a = new MutableEnvelope( new FourLongs(0, 0, 0, 0)); var t1 = 新线程(() -> 线程1(a)); var t2 = new Thread(() -> thread2(a)); t1。开始 (); t2。开始 (); t1。加入 (); t2。加入 (); IO。 println(a.有效负载);编写扁平化字段需要编写其各个组件。如果线程 1 和线程 2 独立编写这些组件,则另一个线程可能会观察到由两个不同分配的部分组装而成的撕裂值,例如 (1, 1, 0, 0) 。 Java 内存模型禁止这种撕裂:两个线程加入后,该程序可能只打印 (1, 0, 0, 0) 或 (0, 1, 0, 0) 。保证这么大的扁平化值的无撕裂分配成本很高,因此当前的 JVM 使用参考布局。每个线程构造一个完整的FourLongs,然后执行原子引用存储。删除身份会删除分配如果我们有一个改变的小函数
这篇文章对您有帮助吗?
订阅66必读
每日精选科技资讯,直达你的邮箱