【问题标题】:Confusion about HotSpot JVM JIT关于 HotSpot JVM JIT 的困惑
【发布时间】:2017-08-20 08:20:36
【问题描述】:

例如,一个方法中有10000次的循环。当它运行1000次时,backedge_counter触发JIT编译。解释器继续执行。当它循环 4000 次时,JIT 编译完成。

我的问题是,remainder 6000次是如何被解释器执行的,还是执行本机代码?或者直到下次调用此方法才执行本机代码? 下次调用这个方法会发生什么?

【问题讨论】:

  • 为什么不能本地执行?这不是 JIT 编译的重点吗?
  • +1 这是一个有趣、重要的问题,恕我直言,不值得反对。 Java HotSpot 编译器是否能够将当前运行的方法调用从解释代码更改为编译代码?问题的措辞并不完美,因为 JIT 和 HotSpot 是微妙不同的方法,但文本明确表示 HotSpot 的意思。
  • @RalfKleberhoff 如果它不能改变一种方法,那么它到底有什么意义呢?也许它让当前的调用完成,但肯定不是下一个 5999 次。这个问题很愚蠢。
  • 当然,HotSpot 能够在此方法未执行时将方法从解释转换为本地。这是理所当然的。在方法执行过程中也可以这样做吗?
  • @RalfKleberhoff 这不是被问到的问题。问题是关于剩余的 6,000 次调用。

标签: java jvm jit hotspot


【解决方案1】:

假设您询问的是 HotSpot JVM,答案是剩余的交互将在编译代码中执行。

HotSpot JVM 有一种称为“堆栈上替换”的技术,可以在方法运行时从解释器切换到编译代码。

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

堆栈替换
也称为“OSR”。转换一个的过程 将(或优化程度较低的)堆栈帧解释为已编译(或更多 优化)堆栈帧。当口译员发现 一个方法正在循环,请求编译器生成一个特殊的 nmethod 在循环中的某处有一个入口点(具体来说,在 后向分支),并将控制权转移到该 nmethod。粗略 反优化。

如果您使用-XX:+PrintCompilation 标志运行JVM,OSR 编译将由% 标志标记:

    274   27       3       java.lang.String::lastIndexOf (52 bytes)
    275   29       3       java.lang.String::startsWith (72 bytes)
    275   28       3       java.lang.String::startsWith (7 bytes)
    275   30       3       java.util.Arrays::copyOf (19 bytes)
    276   32       4       java.lang.AbstractStringBuilder::append (29 bytes)
    276   31  s    3       java.lang.StringBuffer::append (13 bytes)
    283   33 %     3       LoopTest::myLongLoop @ 13 (43 bytes)
             ^                                    ^
            OSR                            bytecode index of OSR entry

更新

通常在 OSR 编译后,常规编译也会排队,因此下次调用该方法时,它将直接以编译模式开始运行。

    187   32 %     3       LoopTest::myLongLoop @ 13 (43 bytes)
    187   33       3       LoopTest::myLongLoop (43 bytes)

但是,如果在再次调用该方法时常规编译尚未完成,则该方法将开始在解释器中运行,然后将在循环内切换到 OSR 条目。

【讨论】:

  • 但是下次调用这个方法会发生什么?
  • 你的回答很有帮助。
【解决方案2】:

让我们重述这个问题:

Java HotSpot 编译器是否能够在执行过程中将方法从解释型更改为编译型?

我觉得可以。

这项任务对引擎来说并不容易(几年前,我在为 PalmOS 手持设备开发名为 JUMP 的 Ahead-of-Time 编译器时收集了该领域的一些经验)。当引擎决定切换时,它至少必须考虑以下几点:

  • 程序计数器在哪里?在解释代码中,它位于方法开头的某个字节码偏移处,确切地知道接下来要执行哪个字节码。在优化的本机代码中,通常 JVM 字节码不会转换为孤立的机器指令块,而是相互依赖、无序重新排列等等。所以切换时可能没有与字节码程序计数器完全对应的native指令地址。

  • 数据在哪里?解释器(可能)将所有内容都保存在堆栈上,优化的原生代码使用寄存器和堆栈分配的混合,这将在原生翻译中的不同位置有所不同。

所以我读了the HotSpot whitepaper。它没有明确回答这个问题,但在“去优化”下有一个提示。当一个新类被动态加载甚至在调试会话中被替换时,以前的优化(如内联)可能会变得无效。

因此,Java HotSpot VM 必须能够动态反优化 (然后在必要时重新优化)先前优化的热点, 即使在为热点执行代码时也是如此。

这也是在编译代码和解释代码之间切换,只是反过来。由于这是 HotSpot 引擎的记录行为,我得出的结论是,在当前执行的方法调用中从解释代码切换到编译代码是可能的。

编辑:

我对问题核心的理解不够明确。

我知道有一种方法可以进行 10000 次迭代的循环,如下所示:

void loop() {
    for (int i=0; i<10000; i++) {
        // example loop body
        objects[i].doSomething();
    }
}

在例如之后HotSpot 编译器优化了该方法 4000 次迭代。然后会发生什么?

有两个方面,一个琐碎的,一个复杂的:

  • 最简单的一点是循环内发生的调用(例如doSomething())将在编译版本可用时立即调用它们。我在最初的回答中没有提到这一点,因为我认为这是理所当然的。

  • 复杂的方面是:当前运行的loop() 执行是否会在 i=4000 时从解释代码切换到编译代码?这就是我理解为 OP 的问题。

【讨论】:

  • 让我们重述这个问题。让我们回答被问到的问题。如果您有自己的问题,请提出,如果您对自己的问题有自己的答案,请在此处发布。坚持到底。​​span>
  • 但是下次调用这个方法会发生什么?
猜你喜欢
  • 1970-01-01
  • 2019-05-17
  • 2020-03-06
  • 2016-03-16
  • 2013-07-27
  • 1970-01-01
  • 2012-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多