Java 已移除偏向锁 - 会影响您吗?

上周我们得知,Java 15 中的 OpenJDK 团队已在 Java 虚拟机中禁用了偏向锁JEP 374)。与之前的版本相比,这一更改可能会对 Java 应用程序的性能产生负面影响。

Red Hat 自家的性能团队目前正在进行性能测试,以了解它对我们的 Java 中间件有何影响,但任何泛泛的测试都无法揭示此更改对真实世界应用程序的影响。

这时就需要您了。

我们希望从您那里获取有关您的应用程序性能是否受偏向锁影响的信息。

为此,请在您的应用程序性能测试中尝试以下操作:

使用 Java 11 (jdk11u),如果可行,也使用 Java 15 (jdk15),并添加以下命令行标志来像往常一样运行您的 Quarkus 应用程序性能测试:

启用:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

禁用:-XX:-UseBiasedLocking

我们希望了解本次测试的结果,无论您是在相同的 Java 虚拟机上看到回归还是没有。

如果可能,使用以下场景运行它也会有很大帮助:

  1. 单线程

  2. 线程数 ~= 硬件核心数

  3. 线程数 ~= N * 硬件核心数,其中 8 < N < 16

这些场景的目的是查看并发级别如何影响结果。

报告结果

打开一个 bug,在标题中包含 [jep374] + 您的项目。例如:[jep374] acme 项目 crazy panda 的结果,并在描述中按每次运行提供以下信息:

jvm used: jdk11 or jdk15
thread-count: 1..N (if you know)
hardware-core count: N (if you know)
performance test result: with biased locking
performance test result: without biased locking

这将极大地帮助我们。谢谢!

背景

以下是一些关于偏向锁的背景信息 - 均可选阅读 - 您无需了解细节即可通过运行性能测试并告知我们是否有任何变化来帮助我们。

什么是偏向锁?

偏向锁降低了/无竞争/同步的成本。

没有偏向锁:当一个线程对同一个对象执行重复同步时,它需要设置和清除锁位。它还需要等待这些设置/清除写入本地缓存后再继续执行其他内存操作。

有偏向锁:当一个线程首次同步一个对象时,它会做更多的工作来获取同步(将其“偏向”于该线程)。后续的同步通过简单的读取测试进行,无需刷新到缓存。

那么权衡是什么呢?好吧,如果偏向锁存在竞争,那么偏向和解除偏向锁的工作量就会更大。然而,众所周知,许多同步操作是没有竞争的。

当一个潜在的并发数据结构实际上是顺序使用时,偏向可以带来巨大的好处。它最有帮助的例子就是我们在 DataOutputStream 类中发现的问题。通常只有一个线程写入 DataOutputStream,并且在流填满之前通常不会被读取。尽管如此,每次调用 putInt()putLong() 都会调用同步方法来将字节计数增加 4 或 8。这是必要的,以防万一其他线程想要可靠地定位有效缓冲区数据的末尾,但这很少发生。因此,无偏向的情况在每次基本 put 操作中都会遭受锁写入和缓存刷新延迟。

ByteOutputStream 类也出现了类似的情况。putByte 方法是同步的。因此,写入单个字节涉及锁定和解锁。请注意,putInt 方法调用 putByte 4 次,需要 4 次锁定和解锁。putLong 调用它 8 次!

为什么要移除偏向锁?

偏向锁的实现给 JVM 增加了巨大的复杂性,只有少数最有经验的工程师才理解它。维护它和围绕它进行设计的成本正在严重拖慢新功能的进展。如果可能,长期目标就是移除它。一些 OpenJDK 贡献者希望在 jdk15 中立即移除它,而另一些人则主张采用更慢的弃用路线,以便检查我们是否真的可以摆脱它。

接下来会发生什么?

我们正在收集 Red Hat 各团队的内部性能测试,并将收集社区报告的测试数据,以查看数据表明了什么。在此阶段,我们不假设移除偏向锁一定会导致性能下降。我们知道在某些情况下,没有偏向锁可以提高性能。我们担心的是,找出像上述 JDK 示例那样可能导致严重性能下降的情况,并了解最坏情况有多糟糕,以及有多普遍。

处理完数据后,我们可能会联系报告异常结果的场景,以获取更多详细信息。

然后,我们将与更广泛的 OpenJDK 社区合作,帮助决定是完全关闭偏向锁,还是需要更长的平缓弃用期。

无论如何,感谢您为使 Java 变得更好所提供的帮助和兴趣!