【问题标题】:why jvm -XX:+EliminateAllocations fail为什么 jvm -XX:+EliminateAllocations 失败
【发布时间】:2020-11-08 15:42:39
【问题描述】:

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);
    }
}

用户.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

发现用户对象一直在堆上创建。理论上,我们不应该用标量替换用户对象,也不应该在堆上创建对象?

【问题讨论】:

标签: java jvm jit


【解决方案1】:

DoEscapeAnalysisEliminateAllocations 标志默认启用 - 无需显式设置。

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 考虑重新编译。考虑到迭代之间有 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 进行此类测试。否则很容易陷入常见的基准测试陷阱之一。这是一个similar question,它也试图衡量分配消除的效果,但做错了。

【讨论】:

  • 当我更改 JVM 参数时 -XX:+PrintCompilation -XX:CompileCommand="print,OnStackTest::alloc" ``` 12944 69 n 0 java.lang.Thread::sleep (native) (static) 12994 70 3 com.zby.learn.jvm.OnStackTest ::alloc(20 字节)12994 71 3 com.zby.learn.jvm.User::(16 字节)271252 72 4 com.zby.learn.jvm.OnStackTest::alloc(20 字节)271252 70 3 com.zby.learn.jvm.OnStackTest::alloc (20 bytes) 未进入 ```
  • 这说明alloc在271252毫秒后被C2(tier 4)编译,出现made not entrant,然后运行jamp -histo | findstr User,用户对象不再增长。但那是因为JIT编译器优化了僵尸代码,与-XX:+PrintCompilation无关。 BTW,-XX:CompileCommand="print,OnStackTest::alloc"是什么意思?
  • @learninJava 这会打印方法的汇编代码,我从中获取了上面的 sn-p。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-16
  • 2015-06-19
  • 2014-06-16
  • 1970-01-01
相关资源
最近更新 更多