变量在循环块中声明

时间:2013-05-01 07:36:33

标签: java

class Sample {

    int a;

    public void abcx() {
        for (int i = 0; i < 5; i++) {
            if (i % 2 == 0) {
                int b = i;
            }
        }
    }
}

分配b的频率是多久?

2 个答案:

答案 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        
}
  • aload_0:在操作数堆栈上加载对象引用(source
  • iconst_n:用于将常量0到5推入堆栈。 (source
  • istore_1:将堆栈顶部的整数存储到变量1
  • invokespecial:(source
  • if_icmpge:从堆栈中弹出前两个整数并进行比较。如果value2大于或等于value1,则执行转移到地址(source
  • irem:从操作数堆栈中弹出两个整数,将value2除以value1(即value2 / value1),计算余数并将int余数推回堆栈(source

回答您的分配问题

我不确定答案是否正确。我猜用户Budda可能是对的: 编辑:不,Budda错了。但让我们解释为什么这是一个很好的猜测。

  

当i为0时分配一次,然后当i为2时再分配   我是4.总共3个。

}一旦关闭,b的范围就结束了。因此,被垃圾收集器“删除”,因为b没有参考。但是你必须考虑primitive data types不在堆上,只有堆由垃圾收集器(source)管理。

当您查看上面的字节代码时,您可能会注意到b仅在一行(istore_2)中更改。所以你可能想看看进程的内存布局。

我不确定Java程序是否也适用,但x86中的进程也是如此。进程在内存中看起来像这样:

enter image description here 来源:My blog :-)这是一个操作系统类的分配。

您可以看到原始数据类型在内存布局中有自己的部分。所以我猜它在加载类时会被分配一次。但我不能告诉你这个猜测的来源,我不确定。

编辑:另请参阅The Architecture of the Java Virtual Machine

答案 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以获取更多信息。

局部变量表中的StartLength字段表示变量的范围,相对于Code表。 Slot是加载和存储指令使用的插槽号。 Name是源代码中变量的名称。 Signature是一个对变量类型进行编码的字符串。这在JVM Specification - Section 4.7.13 - The LocalVariableTable Attribute

中有所描述