我需要解决什么问题
我们最终将从Java 11转换为Java 17,但是一些性能测试失败了,失败了,大约有50%的回归。
在某些测试中,在Java 11中的Java 11与9秒为6秒。
这仅通过切换运行时就会发生。
所以我必须研究为什么测试失败并解决问题。
我为获取一些数据做了什么
首先,我决定使用剖面师进行测试。我正在使用Intellij IDEA Profiler和默认设置进行烟雾测试。
一旦我运行它,我在Java 11分析报告中缺少的火焰图上发现了奇怪的高原。
所以我决定仔细研究该方法。
深层发掘
该方法看起来像这样:
public class LazyInputStream extends InputStream{
private InputStream is;
...
protected synchronized InputStream getInstance() {
if (is == null) {
is = factory.create();
}
return is;
}
...
public int read() throws IOException {
return getInstance().read();
}
}
该方法不会执行任何长时间操作。它仅在需要时创建输入流的实例,并以后使用。
但是我们正在调用外部输入流中每个read
的方法。换句话说,每次我们要阅读下一个数据的下一部分。
该方法是synchronized
保证在多线程环境中的输入流创建的单个实例。
Java 11分析中没有这种高原。我已经重新运行它,并在不同的机器上检查了几次。
谷歌搜索后,我发现了这个JEP,它说偏差锁是从Java 15禁用的。
什么是偏见锁
偏见锁定为java中的synchronized
优化。
当该方法不那么同时,并且只有一个线程通常会获取锁。
JVM在监视器对象中升高了一个标志,该标志有些线程获取锁定,因此重新评价并通过同一线程释放锁定是轻量级的。但是,当另一个线程试图获取偏置锁定时,必须撤销锁。吊销是一个昂贵的操作。
因此,社区决定将偏差锁定并稍后删除。
因为如果您确实有多线程应用程序,则要避免昂贵的偏差锁定吊销,并且如果您的应用程序不是如此多线程,则根本不需要锁。
我如何解决
首先,让我们使用-XX:+UseBiasedLocking
jvm选项进行测试以打开偏置锁。
回归问题消失了,但警告出现了:
OpenJDK 64-Bit Server VM warning: Option UseBiasedLocking was deprecated in version 15.0 and will likely be removed in a future release.
好吧,所以让我们更聪明地实现懒惰的初始化,以避免每次获取锁并使用旧时尚,但仍在工作double-checked locking。
我发现它是由Suppliers.memoize
在guava库中实现的。
public class LazyInputStream extends InputStream {
private final Supplier<InputStream> initializer;
public LazyInputStream(InputStreamFactory factory) {
this.initializer = Suppliers.memoize(() -> {
try {
return factory.create();
} catch (IOException e) {
throw new IllegalStateException("Failed to create input stream", e);
}
});
}
...
protected synchronized InputStream getInstance() {
return initialized.get();
}
...
public int read() throws IOException {
return getInstance().read();
}
备忘录供应商使用了双重检查锁定的2个场变体,但这是因为供应商可以将null
返回为有效值。
public T get() {
if (!initialized) { // boolean flag
synchronized (this) {
if (!initialized) { // double check to avoid races
T t = delegate.get(); // calling delegate to get a value
value = t; // and remember it
initialized = true; // rise the boolean flag
return t;
}
}
}
return value;
}
我重新运行测试后,我发现回归并没有完全消失,因此看起来我们有类似的锁,但是它们在火焰图上不太热以至于无法看到。
最后,我们决定使用转弯偏置锁,并继续处理我们的代码以不断改进。
结论
- ,如果您与我们相似并使用Java 15+,则只能通过打开偏置锁来获得一些性能提升。 但是要重写问题片总是更好的。
- 在每个Java版本中测试您的代码性能。它将帮助您更早地找到回归,只花一点时间来研究奇怪的高原。