【问题标题】:Is there a lightweight method which adds a safepoint in Java 9+是否有一种轻量级方法可以在 Java 9+ 中添加安全点
【发布时间】:2020-10-14 11:07: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();
}

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

编辑:除了分析器,我使用safepoint()Thread.getStackTrace() 获得更好的细节,因此应用程序可以分析自身,例如当执行某项操作的时间过长时。

【问题讨论】:

  • Compiler.enable() 是一个空的 static 方法,根本没有任何效果。我很惊讶它没有在您的 Java 8 运行时中得到优化。除此之外,我不确定我是否理解正确。当然,根据安全点的分析器会扭曲结果。但是您现在尝试的是在代码中插入失真,以更精确地测量已经失真的执行。
  • @Holger 这对于基于样本的分析来说较少,因为它是使用后台线程在生产中捕获为什么我的延迟太高的配置文件。

标签: java jvm java-11


【解决方案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保证循环不会被优化器消除,不会被视为计数。

我通过-XX:+PrintAssembly 验证了安全点轮询指令插入到我调用Safepoint.force() 的任何位置。调用本身大约需要 1 ns。

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

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

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

关于分析,基于安全点的分析器是completely broken in the first place。这实际上是我几年前开始async-profiler 项目的原因。它的目标是促进以低开销和无安全点偏差的方式对 Java 应用程序进行分析。

【讨论】:

  • 如何将force() 设为一个完全空的方法并使用-XX:CompileCommand=dontinline,Safepoint.force 运行应用程序?如果没有volatile读取的负面影响,不是也能达到同样的效果吗?
  • @Holger 在 x86 上,[稳定字段] 的易失性读取除了阻止围绕它的某些 JIT 优化外,不会对性能产生影响。但是这种优化aren't compatible 也带有非内联调用。方法调用需要存储调用者保存的寄存器、构造新帧、检查堆栈溢出。与跳转/返回指令一起,这肯定会比简单的加载有更多的开销。
  • 但是是的,它会达到同样的效果。
  • @PeterLawrey JMC 向准确分析迈出了一大步,但它仍然无法遍历许多有效的 Java 堆栈。这是我的favorite test,几乎所有分析器都失败了,包括 JMC。如果您对 async-profiler 有任何反馈,请在 Github 上打开一个问题或通过其他方式与我联系。谢谢。
  • 使用 async-profiler,通常不需要手动获取堆栈跟踪 - 有多种模式可以自动收集配置文件,基于 CPU 利用率、性能计数器或仅基于挂钟。它有 Java API 来启动和停止分析,添加或删除受监控的线程等。让我知道你的用例,我们将尝试找出 async-profiler 可以如何提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-29
  • 2012-07-08
  • 2014-12-19
  • 2013-05-18
  • 1970-01-01
  • 2014-08-22
相关资源
最近更新 更多