添加范围 {} 括号让变量超出范围是否与调用和结束函数或某种空循环具有相同的效果?

Does adding scope {} brackets to let variables go out of scope have the same effect as calling and ending a function or some sort of empty loop?

我想知道是否可以让变量超出范围,而只在某些代码周围添加方括号,它是否与其他让变量超出范围的方法有什么不同:

0) 添加括号:

private void methodExample() {
    {
        int example = 5;
    }
    //variable "example" out of scope
}

1)函数调用:

private void methodExample() {        
    justMakeItAMethod();
    //variable "example" out of scope
}

private void justMakeItAMethod() {
        int example = 5;
}

2) 循环结束如下:

private void methodExample() {
    do {
        int example = 5;
    } while (false);
    //variable "example" out of scope
}

变量超出堆栈范围的方式是否因示例而异?

我想到这个的原因:[免责声明我知道这是过早优化的情况]我有一个低级函数,其中有许多不同的小的独立代码部分,功能略有不同,在我看来不足以使它成为一个需要函数调用开销的不同函数,但足够的部分宁愿变量已经超出范围。

在 java 中,对象没有析构函数或删除它们的方法,因此您无法让它们释放内存。

无法保证对象在离开其声明范围并且不存在对其的有效引用时会被回收。在像 java 这样为您管理内存的语言中,您提供的这段代码不会对使用的内存产生影响。

所以在 java 中你最好避免这样的结构。它可能会导致错误并且更难理解代码。

I am however not sure if this will also remove the variables from the stack like a function which goes out of scope does?

没有。

What happens here under the hood?

运行时无任何内容。

Does this differ from a function going out of scope

是的。当函数 returns 弹出堆栈并且函数内定义的所有变量都不复存在。包括您所询问的内部范围内的那些。

or a .. while/for .. loop that ends?

这只是内部作用域的另一种情况。运行时没有任何反应。

如果一个栈槽被重用,先前的值当然会消失,这可能导致它引用的对象的GC。然而,仅仅超出范围并不会导致这种情况,或者实际上不会导致任何情况。它不能。没有用于内部 }.

的字节码指令

字节码调查显示了相当有趣的结果。局部变量 table (在堆栈帧内)为每个局部变量都有槽。因此,如果 代码块中的变量 ,则槽将被下一个变量覆盖。这意味着一旦块的执行完成,变量就不再可用。

代码:

public static void main(String[] args) {

    {
        int x ;
        x=5;
        System.out.println(x);
    }

    int y = 1;
    System.out.println(y);
    {
        int x ;
        x=5;
        System.out.println(x);
    }
    int z = 2;
    System.out.println(z);

}


  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0      37     0  args   [Ljava/lang/String;
        2       7     1     x   I // in block
       11      26     1     y   I  
       20       7     2     x   I // in block
       29       8     2     z   I  

因此,有一些 信息被发送到JVM。

行为可能会有所不同,具体取决于您使用的是解释框架还是 JIT 编译框架。在 JIT 编译的框架中,对象可以在最后一次使用变量后标记为空闲,即使它仍在范围内。例如,参见 this answer。在解释帧中,即使在范围结束后,它仍将驻留在堆栈帧变量槽中。另一方面,编译器重用在作用域结束时释放的变量槽。这可能会导致解释帧的有趣结果:

public class LVTest {
    @Override
    protected void finalize() {
        System.out.println("Finalized");
    }

    public static void main(String[] args) throws InterruptedException {
        {
            LVTest t = new LVTest();
        }
        System.out.println("GC!");
        System.gc();
        Thread.sleep(1000);
        System.out.println("GC!");
        System.gc();
        Thread.sleep(1000);
        System.out.println("GC!");
        System.gc();
        Thread.sleep(1000);
        System.out.println("Assign new var!");
        int a = 5;
        System.out.println("GC!");
        System.gc();
        Thread.sleep(1000);
        System.out.println("Finish");
    }
}

输出是这样的:

GC!
GC!
GC!
Assign new var!
GC!
Finalized
Finish

所以无论我们收集多少次垃圾,我们超出范围的对象都不会最终确定。但是在我们分配一个重用相同变量槽的新变量之后,我们最终可以释放原始变量。如果此方法是 JIT 编译的,Finalized 消息会更早出现。

在任何情况下 "scope end" 在字节码中都被编译为空,因此 JVM 不知道您在哪里关闭花括号。如果您认为您的代码将在解释框架中执行,那么在使用后将 null 分配给变量可能比使用块更安全,并希望在它之后创建新变量。尽管如果您的方法的一部分执行了很长时间并且不使用以前创建的变量,那么将您的方法拆分为更小的部分会更好。在 JIT 编译的框架中,你根本不应该优化它:JIT 编译器很聪明。