【问题标题】:Are subcontexts in Java separate rows on the stack?Java中的子上下文是否在堆栈上分开行?
【发布时间】:2011-03-04 22:08:17
【问题描述】:

在 Java 中是这样的:

public void method() {
  if (condition) {
    Object x = ....;
  }
  System.out.println(x); // Error: x unavailable
}

我想知道的是:x 仅限于 if-statement 的范围只是 Java 编译器的一个功能,还是 xif-statement?

【问题讨论】:

  • 真的很重要吗?很明显,x 无法从块外部访问。 Java 不提供任何访问该变量的方法,即使它没有从堆栈中删除。
  • 兴趣问题,日常编程自然无所谓:)
  • 您正在编写自己的 JVM 语言吗? :)
  • 不,我只是个好奇的家伙

标签: java scope stack javac jls


【解决方案1】:

不,代码块没有获得单独的堆栈帧,使用环绕方法之一。

但是,一旦变量离开作用域,它在当前堆栈帧中的位置就可以重新用于其他变量。

堆栈帧的结构和使用在Java Virtual Machine Specification § 3.6 Frames中描述:

每次调用方法时都会创建一个新框架。框架在其方法调用完成时被销毁,无论该完成是正常的还是突然的(它会引发未捕获的异常)。

这明确指定了方法调用和帧之间的 1:1 关系。

【讨论】:

  • 好的。更进一步:这是规范的一部分,还是它通常是如何实现的?
  • 我认为 JVMS(这里比 JLS 更合适)实际上并没有强制要求这种行为——也许我错过了它,但我读了相当一部分。 Hotspot 可能是这样,但它肯定对实施者开放。
  • @musikk:它不要求重用局部变量“slots”,这是一种常见的做法。但是,JVM 规范确实 要求每个方法调用一帧的方法,这意味着代码块没有单独的帧。
  • 仅当您不使用一些晦涩的编译器时才适用,它将 Java 块编译为字节码方法 ;-) 所以,回答@Barts 最初的评论,不,我不相信它是JLS。
  • 但我认为将代码块编译成方法是规范非法的,因为异常堆栈跟踪与源代码不匹配,会混淆 IDE 和调试器。
【解决方案2】:

块是 Java 语言的一部分(structured programming language),而不是已编译字节码(non-structured language)的一部分。

类文件中的方法规范指定该方法总共使用多少个局部变量,高于实际的指令列表。但是无法从字节码中推断出Java代码中的块曾经在哪里。

【讨论】:

  • 我不同意:JVM 规范确实 非常详细地描述了堆栈帧,事实上:JVM Spec §3.6 Frames
  • 实际上,我写道 JLS 没有指定与堆栈帧相关的变量范围。哪个在技术上是正确的,对吧?
【解决方案3】:

首先,在字节码中变量存储在变量槽和变量槽中而不是栈上。该槽可以被另一个变量重用,但不能保证该值会从变量槽中删除。

比如下面这个类

  public class A {
    public void method(boolean condition) {
 6    if (condition) {
 7      Object x = "";
 8      System.out.println(x);
 9    }
10    System.out.println(condition);
    }
  }

被编译成这个字节码:

// class version 50.0 (50)
public class A {
  ...

  // access flags 0x1
  public method(Z)V
   L0
    LINENUMBER 6 L0
    ILOAD 1
    IFEQ L1
   L2
    LINENUMBER 7 L2
    LDC ""
    ASTORE 2
   L3
    LINENUMBER 8 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L1
    LINENUMBER 10 L1
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Z)V
   L4
    LINENUMBER 11 L4
    RETURN
   L5
    LOCALVARIABLE this LA; L0 L5 0
    LOCALVARIABLE condition Z L0 L5 1
    LOCALVARIABLE x Ljava/lang/Object; L3 L1 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

请注意,在第 7 行创建的变量 x 存储在变量 slot 2 中,该变量在第 10 行对应的字节码中仍然可用。

没有关于如何将 Java 语言编译成字节码的规范,除了一些如何正确编译某些语言结构的示例。然而,Java 编译器允许删除未使用的变量。例如。如果 x 已分配,但未在任何地方使用,则允许编译器删除该代码。同样,编译器内联了所有的静态常量。

【讨论】:

    【解决方案4】:

    是的,它确实从堆栈中删除,使得以前被 'x' 占用的槽可以被其他一些局部变量重用。

    【讨论】:

    • 它没有被删除。此外,字节码中的变量存储在变量槽中,而不是堆栈中。该槽可以被另一个变量重用,但不能保证该值会从变量槽中删除。
    猜你喜欢
    • 2014-10-03
    • 1970-01-01
    • 2011-04-08
    • 2020-03-30
    • 2016-08-22
    • 2010-09-17
    • 2018-02-04
    • 2011-08-23
    • 1970-01-01
    相关资源
    最近更新 更多