0
class Sample {

    int a;

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

多久b分配一次?

4

2 回答 2

7

请看一下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        
}
  • aload_0 :在操作数堆栈上加载对象引用(
  • iconst_n :这些用于将常量整数 0 到 5 压入堆栈。(来源
  • istore_1 :将栈顶的整数存入变量 1
  • 调用特殊:(来源
  • if_icmpge :从堆栈中弹出前两个整数并进行比较。如果 value2 大于或等于 value1,则执行分支到地址 ( source )
  • irem:从操作数堆栈中弹出两个整数,将 value2 除以 value1(即 value2 / value1),计算余数并将 int 余数推回堆栈(source

回答您的分配问题

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

当 i 为 0 时分配一次,然后在 i 为 2 时分配一次,当 i 为 4 时分配一次。所以总共 3 个。

一旦}关闭,范围b就结束了。所以它应该被垃圾收集器“删除”,因为b它没有参考。但是您必须考虑原始数据类型不在堆上,只有堆由垃圾收集器(source)管理。

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

我不确定以下是否适用于 Java 程序,但适用于 x86 中的进程。进程在内存中如下所示:

在此处输入图像描述 资料来源:我的博客:-) 这是一个操作系统类的作业。

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

编辑:另见Java 虚拟机的体系结构

于 2013-05-01T07:39:36.423 回答
3

它被分配一次,因为框架中的局部变量数组是在每次方法调用创建框架时分配的。

来自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 运行,使其输出局部变量的个数,-lflag 使其输出每个方法的局部变量表。

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 节 - 局部变量了解更多信息。

局部变量表中的StartandLength字段表示变量相对于Code表的范围。Slot是加载和存储指令使用的槽号。Name是源代码中变量的名称。Signature是一个编码变量类型的字符串。这在JVM 规范 - 第 4.7.13 节 -LocalVariableTable属性中有所描述

于 2013-05-01T08:06:08.257 回答