弹出堆栈时内存会发生什么?

时间:2017-04-16 19:36:42

标签: java c jvm

我有一个功能

public void f() {
    int x = 72;
    return;
}

因此x可能存储在地址0x9FFF

当函数返回时,该地址的内存会发生什么?它还在吗?我仍然是值72?或者它完全无效?

4 个答案:

答案 0 :(得分:2)

Java编程语言和Java虚拟机都没有定义弹出框架后堆栈帧的内存会发生什么。这是一个低级实现细节,被更高级别的抽象所掩盖。事实上,Java语言和JVM字节码使设计无法从堆栈中检索已删除的值(与C / C ++不同)。

然而,在实践中,Java中的堆栈帧将表现为C中的堆栈帧。增长堆栈将使其指针(通常是向下)碰撞并分配空间来存储变量。缩小堆栈通常会使指针向上碰撞并简单地将旧值保留在内存中而不会覆盖它们。如果您对JVM的堆栈内存区域具有低级访问权限,那么这是您应该看到的行为。

请注意,在您尝试读取未初始化的堆栈变量时,在Java中不可能执行类似C的技巧:

static boolean firstTime = true;

public void f() {
    int x;
    if (firstTime) {
        x = 72;
        firstTime = false;
    } else {
        // Compile error: Variable 'x' may not have been initialized
        System.out.println(x);
    }
}

JVM实现中可能存在其他堆栈行为。例如,当弹出框架时,可以将4 KiB虚拟内存页面取消映射回操作系统,这将实际擦除旧值。同样在机器体系结构(例如Mill)上,特别处理堆栈内存,以便增加堆栈将始终返回填充零字节的区域,这样可以节省从内存中实际加载旧值的工作。

答案 1 :(得分:2)

在C中,它是未定义的行为

在实践中,如果你尝试类似的东西:

 int *ptr;

 void foo() {
    bar();
    printf("%d", *ptr);
 }

 void bar() {
     int x = 72;
     ptr = &x;
 }

然后可能在C的大多数实现中,foo()会打印72。这是因为虽然ptr引用的地址可用于重新分配,但它不太可能尚未重新分配,并且没有任何内容覆盖该内存。程序继续运行的时间越长,初始化更多局部变量,调用malloc(),重新使用此内存地址的可能性就越大,值就会改变。

然而,C规范中没有任何内容表明情况必须如此 - 实现只要超出范围就可以为零,或者在运行时出现运行时恐慌你试着读它,或者,好吧,什么 - 那是什么" undefined"装置

作为程序员,你应该注意避免这样做。很多时候它会引起的错误会很明显,但有些时候你会造成间歇性的错误,这是最难追查的错误。

在Java中,虽然内存在超出范围后仍然可能包含72,但实际上无法访问它,因此它不会影响程序员。在Java中访问它的唯一方法就是有一个"官方"引用它,在这种情况下它不会被标记为垃圾收集,并且不会超出范围。

答案 2 :(得分:1)

Java 中的原始类型放在堆栈中(放入frame局部变量数组中)。每次调用方法时都会创建一个新帧:

public void foo() {
    int x = 72; // 'x' will be stored in the array of local variables of the frame
}

框架在其方法调用完成时被销毁。此时,所有局部变量部分结果可能仍然驻留在堆栈上,但它们将被放弃并且不再可用。

答案 3 :(得分:0)

我不是随便看看规格,但我猜这不是技术上定义的。

我实际上在C ++中尝试了类似的东西,事实上它是72(或者在函数调用返回之前放在那里的任何东西)如果我没记错的话,那么机器实际上并没有通过并将0写入该位置或其他内容。

其中一些也是一个实现细节。我也用MIPS汇编语言实现它(如果我可以挖掘它,我将包含一个代码示例)。基本上,当我需要寄存器时,我只是通过我需要的许多局部变量来“增长”堆栈,存储我需要的寄存器中的当前值(因此我可以在以后恢复它们),并重新使用寄存器。如果这是实现,则该值实际上可以包含调用者中的局部变量的值。我不认为这正是Java正在做的事情。

TL; DR 这是一个实现细节,但在C中,至少几率不会覆盖内存中的值,直到实际需要它为止。 Java很难预测。