我有一个以接口为根的类层次结构,并使用抽象基类实现。它看起来像这样:
interface Shape {
boolean checkFlag();
}
abstract class AbstractShape implements Shape {
private boolean flag = false;
protected AbstractShape() { /* compute flag value */ }
public final boolean checkFlag() { return flag; }
}
interface HasSides extends Shape {
int numberOfSides();
}
interface HasFiniteArea extends Shape {
double area();
}
class Square extends AbstractShape implements HasSides, HasFiniteArea {
}
class Circle extends AbstractShape implements HasFiniteArea {
}
/** etc **/
当我使用VisualVM对正在运行的代码进行采样时,看起来AbstractShape.checkFlag()从未内联并消耗总程序运行时间的14%,这对于一种简单的方法来说是偶然的,甚至是对于一个经常调用的方法。
我在基类上标记了方法final,并且(当前)实现“Shape”接口的所有类都扩展了AbstractShape。
我是否正确解释了VisualVM样本结果?有没有办法说服JVM内联这个方法,还是我需要撕掉接口并使用抽象基类? (我不愿意,因为层次结构包括像HasFiniteArea和HasSides这样的接口,这意味着层次结构没有完美的树形式)
编辑:要清楚,这是一种在任何 Universe 应该内联的方法。在2分钟执行期间,它被称为超过 4.2亿次,因为它没有内联并且仍然是虚拟调用,所以它占运行时的14%。我问的问题是是什么阻止了JVM内联这个方法以及如何解决它?
答案 0 :(得分:5)
一个常见的误解是声明一个类或方法最终 通过允许编译器直接插入来提高效率 方法在任何地方都可以内联。这不完全正确;该 编译器无法执行此操作,因为已加载类 运行时,可能与那些只是版本的版本不同 编译。此外,运行时环境和JIT编译器具有 有关哪些类已经加载,并且能够确切的信息 更好地决定何时内联,无论是否 方法是最终的。
另见this article。
答案 1 :(得分:1)
默认编译器阈值为10000. -XX:CompilerThreshold=
这意味着在将方法或循环(对于服务器JVM)编译为本机代码之前必须至少调用10000次。
编译完成后,可以内联,但调用堆栈确实显示了这一点。很聪明,知道内联代码来自另一个方法,你永远不会看到一个截断的调用堆栈。
剖析器尝试示例代码并指定时间。它并不总是做得很好而且你得到的方法显然不是消费者被分配CPU时间的时间。 VisualVM是一个免费的分析器,它是用Java实现的。如果您使用像YourKit这样的分析器,您可以获得更准确的结果,因为它使用本机代码,例如不会造成垃圾。
答案 2 :(得分:0)
从文献中我已经通过声明最终方法来阅读JVM的旧版本,它将确定它将内联转换该方法。 现在,您不必将该方法指定为最终优化代码。
您应该让JVM优化代码,并且只有在您明确不希望覆盖该方法时才使方法成为最终方法。 JVM可能不会使您的方法内联,因为考虑到应用程序的其余代码,它自己的优化速度更快。
答案 3 :(得分:0)
经过大量的实验,在界面上调用时,我无法让Sun JDK 6内联这个方法。
幸运的是,涉及的呼叫站点数量有限,而且正在改变
public void paint(Shape shape) {
if(shape.checkFlag()) { /* do stuff */ }
}
到
public void paint(Shape shape) {
if(((AbstractShape)shape).checkFlag()) { /* .. */ }
}
足以让JVM内联该方法。与原始运行时间为6分钟相比,计算的运行时间下降了13%。