Java编译器是否优化了循环局部变量的创建?

时间:2020-03-02 01:16:18

标签: java compiler-optimization

让我们看看这个例子:

String var;
while (...) {
        var = ...
        // do stuff
}

在这种情况下,我们创建对String对象的引用,并在每次循环迭代中为其分配不同的对象。

现在,在另一个示例中:

while (...) {
        String var = ...
        // do stuff
}

如果我们假设编译器是幼稚的,那么它只会在堆栈每次迭代中分配对String对象的引用。

还是会吗?这是我的问题-Java编译器是否执行此优化?我总是在尽可能大的范围内保留对象声明,因为我对此很担心,但是如果编译器已经这样做了,那么我的工作就少了一点。

提前谢谢!

1 个答案:

答案 0 :(得分:4)

它只会在每次迭代时为堆栈上的String对象分配一个引用。

这不是它的工作方式。堆栈中的变量,即参数和局部变量,是在方法条目中分配(保留)的。

例如如果您有这样的代码:

static void foo() {
    String s;
    for (int i = 0; i < 5; i++) {
        int j = i;
        s = String.valueOf(j);
        bar(s);
    }
    for (int j = 0; j < 5; j++) {
        int k = j;
        s = String.valueOf(k);
        bar(s);
    }
}
static void bar(String s) {
}

对于该代码,将在堆栈上分配3个插槽 1

  • s将位于插槽0中,并将慢速模式用于整个方法

  • i在第一个循环的持续时间内将位于插槽1中。

  • j将在第一个循环的 body 持续时间内位于插槽2中。

  • 另一个j将在第二个循环的持续时间内位于插槽1中。

  • k将在第二个循环的 body 持续时间内位于插槽2中。

如您所见,插槽1和2被重用,但是在方法执行期间没有“分配”。方法开始时只分配/撤消了内存。

1)插槽为4字节/ 32位,即int或引用(带有压缩的Oop)的大小。

如果使用javac -g Test.java进行编译并使用javap -v -c Test.class进行反汇编,则会得到(Java 8的输出)

  static void foo();
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=3, args_size=0
         0: iconst_0
         1: istore_1
         2: iload_1
         3: iconst_5
         4: if_icmpge     24
         7: iload_1
         8: istore_2
         9: iload_2
        10: invokestatic  #2                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
        13: astore_0
        14: aload_0
        15: invokestatic  #3                  // Method bar:(Ljava/lang/String;)V
        18: iinc          1, 1
        21: goto          2
        24: iconst_0
        25: istore_1
        26: iload_1
        27: iconst_5
        28: if_icmpge     48
        31: iload_1
        32: istore_2
        33: iload_2
        34: invokestatic  #2                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
        37: astore_0
        38: aload_0
        39: invokestatic  #3                  // Method bar:(Ljava/lang/String;)V
        42: iinc          1, 1
        45: goto          26
        48: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       9     2     j   I
           14      10     0     s   Ljava/lang/String;
            2      22     1     i   I
           33       9     2     k   I
           38      10     0     s   Ljava/lang/String;
           26      22     1     j   I

如您所见,行stack=2, locals=3, args_size=0显示它将为局部变量保留3个插槽。底部的LocalVariableTable显示哪个局部变量在哪个字节码指令(作用域)的持续时间内使用哪个插槽。

在循环中移动s的声明将重新排列将变量分配给插槽的顺序,即它们使用的插槽,并更改s范围的长度,仅此而已

static void foo() {
    for (int i = 0; i < 5; i++) {
        int j = i;
        String s = String.valueOf(j);
        bar(s);
    }
    for (int j = 0; j < 5; j++) {
        int k = j;
        String s = String.valueOf(k);
        bar(s);
    }
}
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       9     1     j   I
           14       4     2     s   Ljava/lang/String;
            2      22     0     i   I
           33       9     1     k   I
           38       4     2     s   Ljava/lang/String;
           26      22     0     j   I
相关问题