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

2022-09-02 21:37:43

在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();
}

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

编辑:除了分析器之外,我还使用它来获取更好的细节,以便应用程序可以分析自身,例如,当执行操作花费的时间太长时。safepoint()Thread.getStackTrace()


答案 1

在 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++) ;
    }
}

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

我验证了安全点轮询指令是否插入到我调用的地方。调用本身大约需要 1 ns。-XX:+PrintAssemblySafepoint.force()

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

顺便说一句,这里有几个开销低于以下几种本机方法:Thread.holdsLock

Thread.currentThread().isAlive()
Runtime.getRuntime().totalMemory()

至于分析,基于安全点的分析器首先被完全破坏了。这实际上是我几年前开始异步探查器项目的原因之一。其目标是促进对 Java 应用程序的分析,同时降低开销且没有安全点偏差。


答案 2

推荐