它只会在每次迭代时在堆栈上分配一个对 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