什么是方法内联?

44

我一直试图理解这究竟是什么意思:

内联函数

在C ++中,指在类声明中定义的成员函数。(2)编译器替换为函数实际代码的函数调用。关键字inline可用于提示编译器执行成员或非成员函数的正文的内联扩展。

内联

在编译过程中用函数的代码副本替换函数调用。

例如,有这样的写法:

当方法是最终的时,它可以被内联。

在此处: http://www.roseindia.net/javatutorials/final_methods.shtml

你能给我举个例子或者帮助我理解“它可以被内联”是什么意思吗?

谢谢。

---
答案: 当一个函数被内联时,在编译期间将其调用替换为函数的代码副本。在C++中,使用关键字inline来提示编译器执行函数的内联扩展。当方法被声明为final时,也可以进行内联。

1
这会有所帮助:http://java.sun.com/developer/onlineTraining/Programming/JDCBook/perf2.html#vm - codaddict
3个回答

78

内联是Java Just-In-Time编译器执行的一种优化。

如果你有一个方法:

public int addPlusOne(int a, int b) {
  return a + b + 1;
}

你可以这样调用:

public void testAddPlusOne() {
  int v1 = addPlusOne(2, 5);
  int v2 = addPlusOne(7, 13);

  // do something with v1, v2
}

编译器可能会将您的函数调用替换为函数体,因此结果实际上看起来像这样:

public void testAddPlusOne() {
  int v1 = 2 + 5 + 1;
  int v2 = 7 + 13 + 1

  // do something with v1, v2
}
编译器这样做是为了避免实际进行函数调用的开销,这将涉及将每个参数推入堆栈中。这显然只能对非虚函数执行。请考虑如果该方法在子类中被重写并且包含该方法的对象的类型直到运行时才知道会发生什么......编译器如何知道要复制哪些代码: 基类的方法体还是子类的方法体?由于Java中所有方法默认都是虚拟的,因此可以明确标记那些不能被重写的方法为 final (或将它们放入 final 类中)。这将帮助编译器确定该方法永远不会被重写,并且安全地进行内联。(请注意,编译器有时也可以确定非final方法是否可内联。)此外,请注意引用中的单词“may”。不能保证最终方法可内联。有各种方法可以保证方法无法内联,但无法强制编译器进行内联。在任何情况下,它几乎总是比您更了解内联何时有助于还是有害于生成代码的速度。请参见维基百科以获取有关好处和问题的概述。

18
不错的回答,但请注意:如果您有一个未被覆盖的非终态方法,一个好的JIT编译器可以找出它并进行内联。如果您随后加载了一个覆盖该方法的类,它可以撤消内联。 - amara
好的,我已经将它添加到答案正文中了。 - Tom Tresansky
非常详细,感谢这个很棒的答案。 - Tarik
字节码是内联的,还是源代码?或者说,只有在编译成字节码时,JVM才决定是否进行一些内联操作? - Apurva Singh

14

假设您有一个看起来像这样的类:

public class Demo {
    public void method() {
        // call printMessage
        printMessage();
    }

    public void printMessage() {
        System.out.println("Hello World");
    }
}

可以通过以下方式“内联”调用printMessage

public class Demo {
    public void method() {
        // call printMessage
        System.out.println("Hello World"); // <-- inlined
    }

    public void printMessage() {
        System.out.println("Hello World");
    }
}

(实际上这并不是在Java层面完成的(甚至不是在字节码层面),而是在JIT编译期间完成的,但上面的示例说明了内联的概念。)

现在考虑如果printMessage方法被另一个类重载,像这样:

class SubDemo extends Demo {
    public void printMessage() {
        System.out.println("Something else");
    }
}

如果编译器内联调用 Demo.printMessage,那么它会被困在 System.out.println("Hello World"); 中,而实际上对象是 SubDemo 实例时这将是错误的

然而,如果该方法被声明为 final,则无论什么情况下都不会出现这种情况。如果方法是“final”,则意味着它永远不能被新定义所覆盖,因此可以安全地进行内联!


1
也是一个很好的答案,但需要注意的是,如果你有一个非 final 的方法却没有进行重写,一个好的 JIT 可以找出来并将其内联。如果你后来加载了一个重写了该方法的类,它可以撤销这个内联。 - amara
非常明了,感谢您的出色答案。 - Tarik

13

调用一个函数不是免费的。机器必须维护一个堆栈帧,以便在被调用的函数完成时返回到调用代码部分。维护堆栈(包括在此堆栈上传递函数参数)需要时间。

当一个函数内联时,编译器会用函数的代码替换对函数的调用,以便在运行时避免函数调用的性能惩罚。这是编程中的经典权衡之一:运行时代码变得稍微大一些(占用更多内存),但它运行得更快。


谢谢。在第一段中,你说得很对。 - Tarik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接