是否有一种轻量级方法可以在 Java 9+ 中添加安全点

Pet*_*rey 18 java jvm java-11

Java 9+ 中是否有更便宜的方法调用来保持其安全点?

JVM 在运行时移除安全点以提高效率,但这会使分析和监控代码变得更加困难。出于这个原因,我们特意在精心挑选的地方添加了琐碎的调用,以确保存在安全点。

public static void safepoint() {
    if (IS_JAVA_9_PLUS)
        Thread.holdsLock(""); // 100 ns on Java 11
    else
        Compiler.enable(); // 5 ns on Java 8
}

public static void optionalSafepoint() {
    if (SAFEPOINT_ENABLED)
        if (IS_JAVA_9_PLUS)
            Thread.holdsLock("");
        else
            Compiler.enable();
}
Run Code Online (Sandbox Code Playgroud)

在 Java 8 上,这种开销很好,但Compiler.enable()在 Java 9+ 中被优化掉了,所以我们必须使用更昂贵的方法,或者不启用此功能。

编辑:除了分析器之外,我还使用了safepoint()从中获取更好的细节,Thread.getStackTrace()以便应用程序可以对其自身进行分析,例如,当执行操作需要很长时间时。

apa*_*gin 21

在 HotSpot JVM 中,安全点(JVM 可以安全地停止 Java 线程的位置)是

  • 在从非内联方法返回之前;
  • 在反向分支(即在循环中),除非循环被计算在内。循环计数,如果已知迭代次数有限,并且循环变量适合整数类型;
  • 在线程状态转换(原生 -> Java,原生 -> VM);
  • 在 JVM 运行时的阻塞函数中。

以上所有地方,除了向后分支,至少意味着一个方法调用开销。因此,显然放置安全点的最便宜的方法是编写一个非计数循环:

public class Safepoint {
    private static volatile int one = 1;

    public static void force() {
        for (int i = 0; i < one; i++) ;
    }
}
Run Code Online (Sandbox Code Playgroud)

volatile 保证循环不会被优化器消除,并且不会被视为计数。

我确认-XX:+PrintAssembly在我调用的任何地方都插入了安全点轮询指令Safepoint.force()。调用本身大约需要 1 ns。

但是,由于 JDK 8 中的一个错误,安全点轮询的存在还不能保证从不同线程获得的堆栈跟踪的正确性。本机方法调用设置最后一个 Java 帧锚点,从而“修复”堆栈跟踪。我想这就是您选择本机方法的原因。不过,该错误已在 JDK 9+ 中修复。

顺便说一句,这里有几个本地方法的开销低于Thread.holdsLock

Thread.currentThread().isAlive()
Runtime.getRuntime().totalMemory()
Run Code Online (Sandbox Code Playgroud)

至于分析,基于安全点的分析器首先被完全破坏。这实际上是我几年前开始async-profiler项目的一个原因。它的目标是以低开销和没有安全点偏差的方式促进 Java 应用程序的分析。

  • 将 `force()` 设为一个完全空的方法并使用 `-XX:CompileCommand=dontinline,Safepoint.force` 运行应用程序怎么样?如果没有“易失性”读取的负面影响,它是否不会达到相同的效果? (4认同)
  • @Holger在x86上,[稳定字段]的易失性读取除了阻止周围的某些JIT优化之外不会对性能产生影响。但此类优化[也不兼容](/sf/answers/4138136691/) 与非内联调用。方法调用需要存储调用者保存的寄存器、构造新帧、检查堆栈溢出。与跳转/返回指令一起,这肯定会比简单的加载有更多的开销。 (4认同)
  • 但是,是的,它会达到同样的效果。 (3认同)
  • @PeterLawrey JMC 是准确分析的一大进步,但它仍然无法遍历许多有效的 Java 堆栈。这是我最喜欢的测试(https://github.com/apangin/codeone2019-java-profiling/blob/master/src/demo1/StringBuilderTest.java),几乎所有分析器都会失败,包括 JMC。如果您对 async-profiler 有任何反馈,请在 Github 上提出问题或通过其他方式与我联系。谢谢。 (2认同)