class Sample {
int a;
public void abcx() {
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
int b = i;
}
}
}
}
分配b
的频率是多久?
答案 0 :(得分:7)
请看一下nhahtdh的回答。这很好,因为它引用了JLS的相关部分。
我不删除这个答案,因为我希望它能为您提供一些提示,以便您自己解决未来问题的答案(或至少是好的猜测)。它还可能为您的示例代码提供一些其他说明。
首先:如果谈到“在XYZ情况下Java做了什么”,你应该经常问“我的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时再分配 我是4.总共3个。
}
一旦关闭,b
的范围就结束了。因此,应被垃圾收集器“删除”,因为b
没有参考。但是你必须考虑primitive data types不在堆上,只有堆由垃圾收集器(source)管理。
当您查看上面的字节代码时,您可能会注意到b仅在一行(istore_2
)中更改。所以你可能想看看进程的内存布局。
我不确定Java程序是否也适用,但x86中的进程也是如此。进程在内存中看起来像这样:
来源:My blog :-)这是一个操作系统类的分配。
您可以看到原始数据类型在内存布局中有自己的部分。所以我猜它在加载类时会被分配一次。但我不能告诉你这个猜测的来源,我不确定。
答案 1 :(得分:3)
它被分配一次,因为在为每个方法调用创建一个帧时,会分配一个帧中的局部变量数组。
来自JVM Specification - Section 2.6 - Frames(强调我的)
框架用于存储数据和部分结果,以及执行动态链接,返回方法的值以及调度异常。
每次调用方法时都会创建一个新框架。当方法调用完成时,框架将被销毁,无论该完成是正常还是突然(它会抛出未捕获的异常)。 从创建帧的线程的Java虚拟机堆栈(第2.5.2节)分配帧。每个帧都有自己的局部变量数组(第2.6.1节),它自己的操作数堆栈(第2.6.2节),以及对类的运行时常量池(第2.5.5节)的引用目前的方法。
来自JVM Specification - Section 2.6.1 - Local Variables(强调我的)
每个帧(第2.6节)包含一个称为局部变量的变量数组。帧的局部变量数组的长度在编译时确定,并在类或接口的二进制表示中提供,同时提供与帧相关的方法的代码(§4.7.3)。 / p>
我不确定JVM是否会优化整个方法,因为方法中的代码不会写入除局部变量之外的任何内容。
关于javap
的输出。您需要使用-g
编译程序以生成所有调试信息(包括每个方法的局部变量表)。然后使用javap
(-v
)标志运行-verbose
以使其输出局部变量的数量,并使用-l
标志使其输出每个方法的局部变量表。
javac -g Sample.java javap -c -l -v Sample
这是剪裁后的输出,仅包含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 Specification - Section 2.6.1 - Local Variables以获取更多信息。
局部变量表中的Start
和Length
字段表示变量的范围,相对于Code
表。 Slot
是加载和存储指令使用的插槽号。 Name
是源代码中变量的名称。 Signature
是一个对变量类型进行编码的字符串。这在JVM Specification - Section 4.7.13 - The LocalVariableTable
Attribute