Here's the example I tried to reproduce from Java Performance: The Definitive Guide, Page 97 on the topic of Escape Analysis. This is probably what should happen:
getSum()
must get hot enough and with appropriate JVM parameters it must be inlined into the caller main()
.list
and sum
variables do not escape from main()
method they could be marked as NoEscape
hence JVM could use stack-allocation for them instead of heap-allocation.But I ran it through jitwatch and the result showed that getSum()
compiles into native-assembly and doesn't get inlined into main()
. Not to mention consequently stack-allocation didn't happen either.
What am I doing wrong in here ? (I have put the whole code and hotspot log here.)
Here's the code:
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.stream.IntStream;
public class EscapeAnalysisTest {
private static class Sum {
private BigInteger sum;
private int n;
Sum(int n) {
this.n = n;
}
synchronized final BigInteger getSum() {
if (sum == null) {
sum = BigInteger.ZERO;
for (int i = 0; i < n; i++) {
sum = sum.add(BigInteger.valueOf(i));
}
}
return sum;
}
}
public static void main(String[] args) {
ArrayList<BigInteger> list = new ArrayList<>();
for (int i = 1; i < 1000; i++) {
Sum sum = new Sum(i);
list.add(sum.getSum());
}
System.out.println(list.get(list.size() - 1));
}
}
JVM parameters I used:
-server
-verbose:gc
-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:MaxInlineSize=60
-XX:+PrintAssembly
-XX:+LogCompilation
In order to know why something is inlined or not, you can look in the compilation log for the inline_success
and inline_fail
tags.
However to even get something inlined, the caller would have to be compiled, in your case you want an inline in the main
method so the only way this is going to happen is on-stack replacement (OSR). Looking at your log, you can see a few OSR compilations but none of the main
method: there is simply not enough work in your main
method.
You can fix that by increasing the number of iteration of your for
loop. By increasing it to 100_000
, I got a first OSR compilation.
For such a small example is looked at -XX:+PrintCompilation -XX:+PrintInlining
rather than the whole LogCompilation
output and I saw:
@ 27 EscapeAnalysisTest$Sum::getSum (51 bytes) inlining prohibited by policy
That's not very helpful... but looking a bit a HotSpot source code reveals that it's probably because of a policy that prevents C1 compilations to inline methods that have been OSR compiled by C2. In any case, looking at the inlining done by the C1 compilation is not that interesting.
Adding more loop iterations (1_000_000
with a modulo on the argument to Sum
to reduce run time) gets us a C2 OSR of main
with:
@31 EscapeAnalysisTest$Sum::getSum (51 bytes) already compiled into a big method
That part of C2's policy is rather self-descriptive and is controlled by the InlineSmallCode
flag: -XX:InlineSmallCode=4k
tells HotSpot that the threshold for "big method" is at 4kB of native code. On my machine that was enough to get getSum
inlined:
14206 45 % 4 EscapeAnalysisTest::main @ 10 (61 bytes)
@ 25 EscapeAnalysisTest$Sum::<init> (10 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
s @ 31 EscapeAnalysisTest$Sum::getSum (51 bytes) inline (hot)
@ 31 java.math.BigInteger::valueOf (62 bytes) inline (hot)
@ 58 java.math.BigInteger::<init> (77 bytes) inline (hot)
@ 1 java.lang.Number::<init> (5 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 34 java.math.BigInteger::add (123 bytes) inline (hot)
@ 41 java.math.BigInteger::add (215 bytes) inline (hot)
@ 48 java.math.BigInteger::<init> (38 bytes) inline (hot)
@ 1 java.lang.Number::<init> (5 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 34 java.util.ArrayList::add (29 bytes) inline (hot)
@ 7 java.util.ArrayList::ensureCapacityInternal (13 bytes) inline (hot)
@ 6 java.util.ArrayList::calculateCapacity (16 bytes) inline (hot)
@ 9 java.util.ArrayList::ensureExplicitCapacity (26 bytes) inline (hot)
@ 22 java.util.ArrayList::grow (45 bytes) too big
(Note that i never had to use MaxInlineSize
)
For reference here's the modified loop:
for (int i = 1; i < 1_000_000; i++) {
Sum sum = new Sum(i % 10_000);
list.add(sum.getSum());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With