class Sample {
int a;
public void abcx() {
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
int b = i;
}
}
}
}
多久b
分配一次?
class Sample {
int a;
public void abcx() {
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
int b = i;
}
}
}
}
多久b
分配一次?
请看一下nhahtdh的答案。很好,因为它引用了 JLS 的相关部分。
我不会删除这个答案,因为我希望它能给你一些提示,如何自己找到这样的答案(或至少是好的猜测)以解决未来的问题。它还可能为您的示例代码提供一些额外的解释。
首先:如果涉及“Java 在 XYZ 情况下做什么”,您应该经常问“我的 JVM 在 XYZ 情况下做什么”。如果您想回答有关 Java 本身的问题,您应该可以参考Java Language Specification。
编辑:有关此参考,请参阅 nhahtdh 答案。
我想你能做出的最好的猜测是使用 Java Bytecode。现在加载一个对象/数据结构/原始类型和分配一个是有区别的。
分配一个对象意味着你给它一些可以存储它的空间。它只是一个占位符。加载aload_0
意味着对象引用被推入操作数堆栈。下一个操作将从操作数堆栈中获取其操作数。
javac Sample.java
javap -c Sample.class
为您提供 Java 字节码:
Compiled from "Sample.java"
class Sample {
int a;
Sample();
Code:
0: aload_0 // load int a
1: invokespecial #1 // Method java/lang/Object."<init>":()V (every class is a child class of Object)
4: return
public void abcx();
Code:
0: iconst_0 // get 0 on stack
1: istore_1 // store 0 to variable 1 (int i=0)
2: iload_1 // load 0 from variable 1 (load 0 from i)
3: iconst_5 // load 5 from
4: if_icmpge 21 // i<5 (21 means: jump to line 21 if i >= 5)
7: iload_1 // load i
8: iconst_2 // load 2
9: irem // i%2
10: ifne 15 // if(i%2!=0) jump to line 15
13: iload_1 // load i
14: istore_2 // b=i
15: iinc 1, 1 // i++
18: goto 2 // back to loop condition
21: return
}
我不确定正确的答案。我猜用户 Budda 可能是对的:
编辑:不,Budda 是错误的。但是让我们解释一下为什么这是一个很好的猜测。
当 i 为 0 时分配一次,然后在 i 为 2 时分配一次,当 i 为 4 时分配一次。所以总共 3 个。
一旦}
关闭,范围b
就结束了。所以它应该被垃圾收集器“删除”,因为b
它没有参考。但是您必须考虑原始数据类型不在堆上,只有堆由垃圾收集器(source)管理。
当您查看上面的字节码时,您可能会注意到 b 仅在一行 ( istore_2
) 中更改。所以你可能想看看进程的内存布局。
我不确定以下是否适用于 Java 程序,但适用于 x86 中的进程。进程在内存中如下所示:
资料来源:我的博客:-) 这是一个操作系统类的作业。
您可以看到原始数据类型在内存布局中有自己的部分。所以我猜它在加载类时被分配一次。但我不能给你这个猜测的来源,我也不确定。
编辑:另见Java 虚拟机的体系结构。
它被分配一次,因为框架中的局部变量数组是在每次方法调用创建框架时分配的。
来自JVM 规范 - 第 2.6 节 - 框架(强调我的)
框架用于存储数据和部分结果,以及执行动态链接、方法返回值和调度异常。
每次调用方法时都会创建一个新框架。框架在其方法调用完成时被销毁,无论该完成是正常的还是突然的(它会引发未捕获的异常)。帧是从创建帧的线程的 Java 虚拟机堆栈(第 2.5.2 节)分配的。每个帧都有自己的局部变量数组(第 2.6.1 节)、自己的操作数堆栈(第 2.6.2 节)以及对当前方法类的运行时常量池的引用(第 2.5.5 节) .
来自JVM 规范 - 第 2.6.1 节 - 局部变量(强调我的)
每个帧(第 2.6 节)包含一个变量数组,称为局部变量。帧的局部变量数组的长度在编译时确定,并以类或接口的二进制表示形式提供,以及与帧关联的方法的代码(第 4.7.3 节)。
我不确定 JVM 是否会优化整个方法,因为方法中的代码不会写入除局部变量之外的任何内容。
至于输出javap
。您需要编译您的程序-g
以生成所有调试信息(包括每个方法的局部变量表)。然后javap
用-v
( -verbose
) flag 运行,使其输出局部变量的个数,-l
flag 使其输出每个方法的局部变量表。
javac -g 示例.java javap -c -l -v 示例
这是修剪后的输出,其中仅包含abcx()
方法:
public void abcx();
LineNumberTable:
line 6: 0
line 8: 7
line 10: 13
line 6: 15
line 13: 21
LocalVariableTable:
Start Length Slot Name Signature
15 0 2 b I
2 19 1 i I
0 22 0 this LSample;
Code:
Stack=2, Locals=3, Args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 21
7: iload_1
8: iconst_2
9: irem
10: ifne 15
13: iload_1
14: istore_2
15: iinc 1, 1
18: goto 2
21: return
LineNumberTable:
line 6: 0
line 8: 7
line 10: 13
line 6: 15
line 13: 21
LocalVariableTable:
Start Length Slot Name Signature
15 0 2 b I
2 19 1 i I
0 22 0 this LSample;
StackMapTable: number_of_entries = 3
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 12 /* same */
frame_type = 250 /* chop */
offset_delta = 5
}
请注意,这Locals=3
意味着框架中有 3 个局部变量,但并不一定意味着代码中有 3 个局部变量。对于代码和JVM,“局部变量”的定义是不同的。阅读JVM 规范 - 第 2.6.1 节 - 局部变量了解更多信息。
局部变量表中的Start
andLength
字段表示变量相对于Code
表的范围。Slot
是加载和存储指令使用的槽号。Name
是源代码中变量的名称。Signature
是一个编码变量类型的字符串。这在JVM 规范 - 第 4.7.13 节 -LocalVariableTable
属性中有所描述