【问题标题】:Does the Java compiler optimize the creation of loop-local variables? [duplicate]Java 编译器是否优化了循环局部变量的创建? [复制]
【发布时间】:2020-03-02 01:16:18
【问题描述】:

我们来看这个例子:

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

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

现在,在另一个例子中:

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

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

或者会吗?那是我的问题 - (a?) Java 编译器是否执行此优化?我总是将对象声明保留在尽可能广泛的范围内,因为我担心这一点,但如果编译器已经这样做了,那我的鞋子上就少了一颗鹅卵石。

提前谢谢你!

【问题讨论】:

  • JIT会对其进行优化,Java有两个编译阶段;字节码编译器,然后是 JIT。
  • @Elliott 介意详细说明吗?感谢您的回复!
  • JIT 不会优化它,因为没有什么可以优化的。它不做这些事情中的任何一个。整个堆栈帧在进入函数时分配。如果您检查这两个示例的字节码,您会发现它是相同的。 @ElliottFrisch

标签: java compiler-optimization


【解决方案1】:

它只会在每次迭代时在堆栈上分配一个对 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 将在 slot 0 中,并在整个方法中使用慢速

  • i 在第一个循环期间将位于插槽 1。

  • j 将在第一个循环的 body 期间位于插槽 2。

  • 另一个 j 将在第二个循环期间位于插槽 1。

  • k 将在第二个循环的 body 期间位于插槽 2。

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

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

如果你用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

【讨论】:

  • 糟糕,我再次创建了一个重复的答案:stackoverflow.com/a/34341363/5221149
  • 很好的解释,这正是我想要的!不幸的是,我已经花费了我所有的日常支持,但我一定会回来并为答案 +1 :)。干杯!
猜你喜欢
  • 2018-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-17
  • 1970-01-01
  • 2016-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多