1. Introduction
1.介绍
In this tutorial, we’ll take a look at what method inlining is in the Java Virtual Machine and how it works.
在本教程中,我们将看看什么是Java虚拟机中的方法内联以及它是如何工作的。
We’ll also see how to get and read the information related to inlining from the JVM and what we can do with this information in order to optimize our code.
我们还将看到如何从JVM中获取和读取与内联有关的信息,以及我们可以利用这些信息来优化我们的代码。
2. What Method Inlining Is?
2.什么是内联法?
Basically, inlining is a way to optimize compiled source code at runtime by replacing the invocations of the most often executed methods with its bodies.
基本上,内联是一种在运行时优化已编译的源代码的方法,即用其主体取代最经常执行的方法的调用。
Although there’s compilation involved, it’s not performed by the traditional javac compiler, but by the JVM itself. To be more precise, it’s the responsibility of the Just-In-Time (JIT) compiler, which is a part of the JVM; javac only produces a bytecode and lets JIT do the magic and optimize the source code.
虽然涉及到编译,但它不是由传统的javac编译器执行的,而是由JVM本身执行的。更准确地说,是由JIT编译器负责,它是JVM的一部分;javac只产生一个字节码,让JIT施展魔法并优化源代码。
One of the most important consequences of this approach is that if we compile the code using old Java, the same .class file will be faster on newer JVMs. This way we don’t need to recompile the source code, but only update Java.
这种方法最重要的后果之一是,如果我们使用旧的Java编译代码,同样的.class文件在较新的JVM上将会更快。这样我们就不需要重新编译源代码,而只需要更新Java。
3. How JIT Does It?
3.JIT是如何做到的?
Essentially, the JIT compiler tries to inline the methods that we often call so that we can avoid the overhead of a method invocation. It takes two things into consideration when deciding whether to inline a method or not.
本质上,JIT编译器试图内联我们经常调用的方法,这样我们就可以避免方法调用的开销。在决定是否内联一个方法时,它要考虑两件事。
First, it uses counters to keep track of how many times we invoke the method. When the method is called more than a specific number of times, it becomes “hot”. This threshold is set to 10,000 by default, but we can configure it via the JVM flag during Java startup. We definitely don’t want to inline everything since it would be time-consuming and would produce a huge bytecode.
首先,它使用计数器来记录我们调用该方法的次数。当该方法被调用超过一个特定的次数时,它就成为 “热”。这个阈值默认设置为10,000,但我们可以在Java启动时通过JVM标志来配置它。我们肯定不想内联所有的东西,因为这样做会很费时间,而且会产生一个巨大的字节码。
We should keep in mind that inlining will take place only when we get to a stable state. This means that we’ll need to repeat the execution several times to provide enough profiling information for the JIT compiler.
我们应该记住,内联只有在我们达到稳定状态时才会发生。这意味着,我们需要重复执行几次,以便为JIT编译器提供足够的剖析信息。
Furthermore, being “hot” does not guarantee that the method will be inlined. If it’s too big, the JIT won’t inline it. The acceptable size is limited by the -XX:FreqInlineSize= flag, which specifies the maximum number of bytecode instructions to inline for a method.
此外,”热 “并不保证该方法会被内联。如果它太大了,JIT就不会内联它。可接受的大小受-XX:FreqInlineSize=标志的限制,它指定了一个方法内联的最大字节码指令数。
Nevertheless, it’s strongly recommended to not change the default value of this flag unless we’re absolutely certain of knowing what impact it could make. The default value depends on the platform – for 64-bit Linux, it’s 325.
尽管如此,我们强烈建议不要改变这个标志的默认值,除非我们绝对确定知道它可能产生什么影响。默认值取决于平台–对于64位Linux,它是325。
The JIT inlines static, private, or final methods in general. And while public methods are also candidates for inlining, not every public method will necessarily be inlined. The JVM needs to determine that there’s only a single implementation of such a method. Any additional subclass would prevent inlining and the performance will inevitably decrease.
JIT对static、private、或final方法进行内联,一般来说。虽然public方法也是内联的候选者,但并不是每个公共方法都必须被内联。JVM需要确定这样的方法只有一个实现。任何额外的子类都会阻止内联,性能将不可避免地下降。
4. Finding Hot Methods
4.寻找热点方法
We surely don’t want to guess what the JIT is doing. Therefore, we need some way to see which methods are inlined or not inlined. We can easily achieve this and log all this information to the standard output by setting some additional JVM flags during startup:
我们肯定不想猜测JIT在做什么。因此,我们需要一些方法来查看哪些方法被内联或未内联。我们可以通过在启动过程中设置一些额外的JVM标志,轻松实现这一目标,并将所有这些信息记录到标准输出。
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
The first flag will log when JIT compilation happens. The second flag enables additional flags including -XX:+PrintInlining, which will print what methods are getting inlined and where.
第一个标志将记录JIT编译发生的时间。第二个标志可以启用额外的标志,包括-XX:+PrintInlining,它将打印哪些方法被内联,在哪里被内联。
This will show us the inlined methods in the form of a tree. The leaves are annotated and marked with one of the following options:
这将以树的形式向我们展示内联方法。叶子上有注释,并标有以下选项之一。
- inline (hot) – this method is marked as hot and is inlined
- too big – the method is not hot, but also its generated bytecode is too big, so it’s not inlined
- hot method too big – this is a hot method, but it’s not inlined since the bytecode is too big
We should pay attention to the third value and try to optimize methods with the label “hot method too big”.
我们应该注意第三个数值,并尝试优化带有 “热方法太大 “标签的方法。。
Generally, if we find a hot method with a very complex conditional statement, we should try to separate the content of the if-statement and increase the granularity so that the JIT can optimize the code. The same goes for the switch and for-loop statements.
一般来说,如果我们发现一个热的方法有非常复杂的条件语句,我们应该尽量把if-语句的内容分开,增加颗粒度,这样JIT就可以优化代码。对于switch和for-loop语句也是如此。
We can conclude that a manual method inlining is something that we don’t need to do in order to optimize our code. The JVM does it more efficiently, and we would possibly make the code long and hard to follow.
我们可以得出结论,为了优化我们的代码,手动内联方法是我们不需要做的。JVM会更有效地做到这一点,而我们可能会使代码变得冗长而难以理解。
4.1. Example
4.1.例子
Let’s now see how we can check this in practice. We’ll first create a simple class that calculates the sum of the first N consecutive positive integers:
现在让我们看看如何在实践中检验这一点。我们首先创建一个简单的类,计算前N个连续正整数的和。
public class ConsecutiveNumbersSum {
private long totalSum;
private int totalNumbers;
public ConsecutiveNumbersSum(int totalNumbers) {
this.totalNumbers = totalNumbers;
}
public long getTotalSum() {
totalSum = 0;
for (int i = 0; i < totalNumbers; i++) {
totalSum += i;
}
return totalSum;
}
}
Next, a simple method will make use of the class to perform the calculation:
接下来,一个简单的方法将利用这个类来进行计算。
private static long calculateSum(int n) {
return new ConsecutiveNumbersSum(n).getTotalSum();
}
Finally, we’ll call the method a various number of times and see what happens:
最后,我们将调用该方法若干次,看看会发生什么。
for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) {
calculateSum(i);
}
In the first run, we’re going to run it 1,000 times (less than the threshold value of 10,000 mentioned above). If we search the output for the calculateSum() method, we won’t find it. This is expected since we didn’t call it enough times.
在第一次运行中,我们要运行1000次(低于上面提到的10,000次的阈值)。如果我们在输出中搜索calculateSum()方法,我们将找不到它。这是意料之中的,因为我们没有调用足够的次数。
If we now change the number of iterations to 15,000 and search the output again, we’ll see:
如果我们现在把迭代次数改为15,000次,并再次搜索输出结果,我们会看到。
664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
@ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)
We can see that this time the method fulfills the conditions for inlining and the JVM inlined it.
我们可以看到,这一次该方法满足了内联的条件,JVM将其内联。
It’s noteworthy to mention again that if the method is too big, the JIT won’t inline it, regardless of the number of iterations. We can check this by adding another flag when running the application:
值得再次提及的是,如果方法太大,JIT不会内联它,不管有多少次迭代。我们可以通过在运行应用程序时添加另一个标志来检查这一点。
-XX:FreqInlineSize=10
As we can see in the previous output, the size of our method is 12 bytes. The -XX:FreqInlineSize flag will limit the method size eligible for inlining to 10 bytes. Consequentially, the inlining shouldn’t take place this time. And indeed, we can confirm this by taking another look at the output:
正如我们在前面的输出中看到的,我们的方法的大小是12字节。-XX:FreqInlineSize标志将限制符合内联条件的方法大小为10字节。因此,这次的内联不应该发生。事实上,我们可以通过再看一下输出来确认这一点。
330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
@ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big
Although we’ve changed the flag value here for illustration purposes, we must emphasize the recommendation of not changing the default value of the -XX:FreqInlineSize flag unless absolutely necessary.
尽管我们在这里为了说明问题而改变了标志值,但我们必须强调,除非绝对必要,否则不要改变-XX:FreqInlineSize标志的默认值的建议。
5. Conclusion
5.总结
In this article, we saw what method inlining is in the JVM and how the JIT does it. We described how we can check if our methods are eligible for inlining or not and suggested how to make use of this information by trying to reduce the size of frequently called long methods that are too big to get inlined.
在这篇文章中,我们看到了什么是JVM中的方法内联以及JIT是如何做到的。我们描述了如何检查我们的方法是否有资格被内联,并建议如何利用这一信息,尝试减少频繁调用的长方法的大小,这些方法太大,无法被内联。
Finally, we illustrated how we can identify a hot method in practice.
最后,我们说明了如何在实践中确定一个热点方法。
All of the code snippets mentioned in the article can be found in our GitHub repository.
文章中提到的所有代码片段都可以在我们的GitHub资源库中找到。