Java 编译器是否优化了循环局部变量的创建?
Does the Java compiler optimize the creation of loop-local variables?
我们来看这个例子:
String var;
while (...) {
var = ...
// do stuff
}
在这种情况下,我们创建一个对 String
对象的引用,并在循环的每次迭代中为其分配不同的对象。
现在,在另一个示例中:
while (...) {
String var = ...
// do stuff
}
如果我们假设编译器是天真的,它只会在堆栈上分配对 String
对象的引用 每次迭代。
或者会吗?这是我的问题 - (a?) Java 编译器是否执行此优化?我总是将对象声明保留在尽可能广泛的范围内,因为我担心这一点,但如果编译器已经这样做了,我的鞋子就少了一颗鹅卵石。
提前致谢!
it'll just allocate a reference to a String object on stack every iteration.
事情不是这样的。堆栈上的变量,即参数和局部变量,在方法入口时分配(保留)。
例如如果你有这样的代码:
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
将在插槽 2 中,持续时间为第一个循环的 body。
另一个 j
将在插槽 1 中,持续第二个循环。
k
将在插槽 2 中,在第二个循环的 body 期间。
如您所见,插槽 1 和 2 被重复使用,但在方法执行期间没有 "allocation" 继续。只有方法开头的内存allocated/reversed.
1) 槽为 4 字节/32 位,即 int
或引用(压缩 Oops)的大小。
如果你用 javac -g Test.java
编译并用 javap -v -c Test.class
反汇编,你会得到 (output from with 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
我们来看这个例子:
String var;
while (...) {
var = ...
// do stuff
}
在这种情况下,我们创建一个对 String
对象的引用,并在循环的每次迭代中为其分配不同的对象。
现在,在另一个示例中:
while (...) {
String var = ...
// do stuff
}
如果我们假设编译器是天真的,它只会在堆栈上分配对 String
对象的引用 每次迭代。
或者会吗?这是我的问题 - (a?) Java 编译器是否执行此优化?我总是将对象声明保留在尽可能广泛的范围内,因为我担心这一点,但如果编译器已经这样做了,我的鞋子就少了一颗鹅卵石。
提前致谢!
it'll just allocate a reference to a String object on stack every iteration.
事情不是这样的。堆栈上的变量,即参数和局部变量,在方法入口时分配(保留)。
例如如果你有这样的代码:
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
将在插槽 2 中,持续时间为第一个循环的 body。另一个
j
将在插槽 1 中,持续第二个循环。k
将在插槽 2 中,在第二个循环的 body 期间。
如您所见,插槽 1 和 2 被重复使用,但在方法执行期间没有 "allocation" 继续。只有方法开头的内存allocated/reversed.
1) 槽为 4 字节/32 位,即 int
或引用(压缩 Oops)的大小。
如果你用 javac -g Test.java
编译并用 javap -v -c Test.class
反汇编,你会得到 (output from with 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