【问题标题】:Visibility and javac / JVM inlining可见性和 javac / JVM 内联
【发布时间】:2014-10-01 15:23:34
【问题描述】:

方法/字段可见性如何影响 Java 中的方法内联?

我想到的情况类似于 public 获取 private 字段的方法:

private Thing blah;

public Thing getBlah() {
    return blah;
}

这里出现了几个问题。

首先,Java 编译器本身是否进行内联?这个问题似乎得到了不同的回答,有人说yes,有人说no。不知道是因为以前不做内联,现在做了,还是只是有些人是对的,有些人是错的……

现在,如果它确实做了一些内联,我是否认为它不可能内联 getBlah() 调用?它们显然是内联有用的地方,因为该方法非常简单,并且调用方法的开销与方法本身的代码一样大。但是如果它被编译器内联,你最终会得到直接访问private 字段的字节码;那么JVM肯定会抱怨吗? (即使这个方法是static final,这也适用。)

其次,JIT 编译器呢?据我所知,当涉及到该级别的内联时,这个问题并不适用。一旦生成本机代码,JVM 就已经完成了检查,并确认我可以调用该方法(因为它是 public);因此它可以生成内联调用的本机代码,而不会出现任何可见性问题......是吗?

【问题讨论】:

    标签: java jvm javac jit inlining


    【解决方案1】:

    javac 编译器(和 any 有效的 java 编译器)不会也不能内联 getter;想一想:您可以从该类扩展一个类并覆盖 getter。是的,如果编译器过于热心地内联该访问,它将不会通过验证器(至少它不应该不通过验证器,但它们不会验证所有内容 - 在 java 1.3 中,你甚至可以使 main () 私有的,它仍然可以工作......同样,javac 中曾经有一个 -O 选项,确实有时会搞砸你的代码)。

    JIT 完全是另外一头野兽;它在任何时候知道(至少现在是这样)是否存在方法的覆盖。即使稍后加载了覆盖 getter 的类,它也可以取消优化已经 JIT 的方法以反映继承树上的更改(这是 AOT 编译器缺少信息的优化之一)。 p>

    因此它可以安全地内联它想要的任何东西。它也不需要人为地维护访问修改器,因为在编译的机器代码中没有这样的东西,并且它再次知道什么是有效的代码转换(并且由于 getter 如此普遍,它对于 JIT 来说也是一个唾手可得的果实优化)。

    编辑:为了清楚起见,上面的段落讨论了潜在的虚拟方法;特别是那些不是私有的、静态的或最终的。 Javac 在某些情况下可以执行内联;因为它可以证明那些不存在覆盖。面对 JIT 也能做到这一点的事实,这将是一项毫无意义的工作,而且它在这方面做得更好。

    【讨论】:

    • 我相信你javac不会内联。无论如何,如果 JIT 以后可以做到的话,也许没有任何意义。但我不确定我是否看到原则上无法做到。当然,对于final 方法,在子类中没有覆盖它的余地;但更重要的是,方法不需要总是内联或从不内联。编译器可以看到在这个特定的上下文中该方法没有被覆盖,并为这个调用内联它......不是吗?它不需要全部或全部。
    • 这不是全部或全部,您的结论在技术上是完全合理的。如果该方法显然是非虚拟的(私有的、最终的或静态的),它可能已经被编译器内联。我说的是 bukl 案例(OP 专门询问了吸气剂和表亲),尽管从上下文来看这已经足够清楚了。因此,虽然 javac 的范围存在一些机会;批量仍然只能由 JIT 完成。现在,当我们已经拥有一个可以做到这一点的 JIT 时,为什么要在 javac 中重复这项工作?
    【解决方案2】:
    1. javac 没有内联方法,甚至像 getBlah()setBlah() 这样简单
    2. 对于 HotSpot JVM,JIT 编译器确实内联所有此类方法,除非它达到内联的最高级别 (-XX:MaxInlineLevel)
    3. JIT 在内联方面同等对待 publicprivate 方法。访问修饰符通常不会影响内联,除非有一些 very specific cases

    【讨论】:

      【解决方案3】:

      任何特定的 Java 编译器(例如 Oracle 的编译器)是否执行任何内联是一个实现细节,您最好忽略它。您选择的编译器的下一代或替代编译器的运行方式可能与您现在看到的不同。

      然而,就 Java 编译器执行内联的程度而言,它只能内联 private 方法和构造函数。其他任何东西都可以从(不可知的)其他类访问。​​

      另一方面,JIT 编译器几乎可以做任何它想做的事情,包括内联公共方法。它负责在加载新类时可能需要的任何调整。 Bean 样式的访问器方法特别适合 JIT 进行优化,它们是如此常见的模式。

      【讨论】:

      • java 编译器可以定义为不能内联(getter);当方法被子类覆盖时,它将无法修复。
      • 我不明白为什么 Java 编译器不应该选择性地内联最终公共方法。它不能将公共方法标记为 always 进行内联,但它可以在适当的地方内联它。如果它是最终的,那么它就不能被覆盖。
      • 请注意括号中的小“吸气剂”。 private getter 非常少见,而且 getter 通常也不是最终的。
      猜你喜欢
      • 2021-05-07
      • 1970-01-01
      • 2019-02-11
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 2016-03-29
      • 2018-10-26
      • 2011-11-19
      相关资源
      最近更新 更多