为什么 jvm -XX:+EliminateAllocations 失败
why jvm -XX:+EliminateAllocations fail
OnStackTest.java
public class OnStackTest {
public static void alloc() {
User u = new User();
u.id = 5;
u.name = "test";
}
public static void main(String[] args) throws InterruptedException {
long b = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
Thread.sleep(50);
alloc();
}
long e = System.currentTimeMillis();
System.out.println(e - b);
}
}
User.java
public class User {
public int id = 0;
public String name = "";
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
JVM 标志
-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
使用jmap -histo
发现一直在堆上创建了user对象。理论上,我们不应该用标量替换用户对象,也不要在堆上创建对象?
DoEscapeAnalysis
和 EliminateAllocations
标志默认启用 - 无需明确设置它们。
EliminateAllocations
标志是C2编译器特有的,声明在c2_globals.hpp中。但是在你的测试中,这个方法很长时间都没有被 C2 编译过。添加 -XX:+PrintCompilation
标志以确保:
...
1045 84 3 java.lang.StringBuffer::<init> (6 bytes)
1045 85 s 3 java.lang.StringBuffer::toString (36 bytes)
1045 86 3 java.util.Arrays::copyOf (19 bytes)
15666 87 n 0 java.lang.Thread::sleep (native) (static)
15714 88 3 OnStackTest::alloc (20 bytes)
311503 89 4 OnStackTest::alloc (20 bytes)
311505 88 3 OnStackTest::alloc (20 bytes) made not entrant
这表明alloc
在15秒后由C1(第3层)编译。一个方法需要调用数千次才能被 C2 考虑为 re-compilation。鉴于迭代之间有 50 毫秒的延迟,这不会很快发生。在我的实验中,alloc
仅在 运行.
5 分钟后才被 C2 编译
C2 编译方法不再包含分配。
我用 -XX:CompileCommand="print,OnStackTest::alloc"
验证了这一点
# {method} {0x0000000012de2bc0} 'alloc' '()V' in 'OnStackTest'
# [sp+0x20] (sp of caller)
0x0000000003359fc0: sub rsp,18h
0x0000000003359fc7: mov qword ptr [rsp+10h],rbp ;*synchronization entry
; - OnStackTest::alloc@-1 (line 4)
0x0000000003359fcc: add rsp,10h
0x0000000003359fd0: pop rbp
0x0000000003359fd1: test dword ptr [0df0000h],eax
; {poll_return}
0x0000000003359fd7: ret
顺便说一句,我建议使用 JMH for such kind of tests. Otherwise it's too easy to fall into one of common benchmarking pitfalls. Here is a ,它也试图衡量分配消除的效果,但它错了。
OnStackTest.java
public class OnStackTest {
public static void alloc() {
User u = new User();
u.id = 5;
u.name = "test";
}
public static void main(String[] args) throws InterruptedException {
long b = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
Thread.sleep(50);
alloc();
}
long e = System.currentTimeMillis();
System.out.println(e - b);
}
}
User.java
public class User {
public int id = 0;
public String name = "";
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
JVM 标志
-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
使用jmap -histo
发现一直在堆上创建了user对象。理论上,我们不应该用标量替换用户对象,也不要在堆上创建对象?
DoEscapeAnalysis
和 EliminateAllocations
标志默认启用 - 无需明确设置它们。
EliminateAllocations
标志是C2编译器特有的,声明在c2_globals.hpp中。但是在你的测试中,这个方法很长时间都没有被 C2 编译过。添加 -XX:+PrintCompilation
标志以确保:
...
1045 84 3 java.lang.StringBuffer::<init> (6 bytes)
1045 85 s 3 java.lang.StringBuffer::toString (36 bytes)
1045 86 3 java.util.Arrays::copyOf (19 bytes)
15666 87 n 0 java.lang.Thread::sleep (native) (static)
15714 88 3 OnStackTest::alloc (20 bytes)
311503 89 4 OnStackTest::alloc (20 bytes)
311505 88 3 OnStackTest::alloc (20 bytes) made not entrant
这表明alloc
在15秒后由C1(第3层)编译。一个方法需要调用数千次才能被 C2 考虑为 re-compilation。鉴于迭代之间有 50 毫秒的延迟,这不会很快发生。在我的实验中,alloc
仅在 运行.
C2 编译方法不再包含分配。
我用 -XX:CompileCommand="print,OnStackTest::alloc"
# {method} {0x0000000012de2bc0} 'alloc' '()V' in 'OnStackTest'
# [sp+0x20] (sp of caller)
0x0000000003359fc0: sub rsp,18h
0x0000000003359fc7: mov qword ptr [rsp+10h],rbp ;*synchronization entry
; - OnStackTest::alloc@-1 (line 4)
0x0000000003359fcc: add rsp,10h
0x0000000003359fd0: pop rbp
0x0000000003359fd1: test dword ptr [0df0000h],eax
; {poll_return}
0x0000000003359fd7: ret
顺便说一句,我建议使用 JMH for such kind of tests. Otherwise it's too easy to fall into one of common benchmarking pitfalls. Here is a