【问题标题】:Java heap & stackJava 堆和栈
【发布时间】:2012-02-06 23:06:27
【问题描述】:

我想再次学习 Java,因为几年前我离开了它。读了一本书,我在理解 Java 如何在堆和堆栈中分配内存时遇到了问题。

这是我所理解的——我会尝试用例子来谈论它。

class TestA {
    int a;

    void methodA(int b) {
        a = b;
    }

    int getA() {
        return a;
    }
}

这是一个展示不同情况的示例类。这是我的主要内容:

int b = 3;

TestA obj = new TestA();
obj.methodA(b);
obj.getA();

那么会发生什么?


##开始

STACK - 为 main 函数占用一些内存

堆 - 空


## int b = 3

STACK - [为 main 函数占用一些内存 -> 这里我们有 b]

堆 - [空]


## TestA obj = new TestA()

STACK - [为 main 函数占用一些内存 -> 这里我们有 b 和对 TestA 的引用]

HEAP - [为 int a 占用一些内存]


## obj.methodA(b);

STACK - [为 main 函数占用一些内存 -> 这里我们有 b 和对 TestA 的引用]

HEAP - [为 int a 占用一些内存] AND [为方法 A 占用另一个内存]


##执行方法A(int b)

STACK - [为 main 函数占用一些内存 -> 这里我们有 b 和对 TestA 的引用] AND [为 methodA() 占用内存 -> 这里我们在这个函数中使用了 b]

HEAP - [为 int a 占用一些内存] AND [为方法 A 占用另一个内存]


我们有:

  • 堆中的对象和实例字段(原始或非原始)
  • 堆栈中的函数和范围值

对吗?

【问题讨论】:

  • 所有对象都分配在堆上。基元以及对对象的引用都在堆栈中。
  • 您还可以跟踪堆内存使用情况,以便通过实验确认/否定您的理论。
  • 看起来可行。但是我已经从事 Java 13 年了,我真的很想知道你为什么关心它。拥有垃圾收集器会使您无法专注于此。
  • @BrianRoach:并非所有原语都在堆栈上。只有原语是函数中的变量。实例(或类)的原始数据成员是堆上实例(或类)内存结构的一部分。
  • @Snicolas:确实,Java、C#、JavaScript 等的乐趣之一。人。是我们几乎从来不用关心这个。只有在调整 JVM 时才是真正的。

标签: java heap-memory stack-memory


【解决方案1】:

默认情况下,所有对象都分配在堆上。但是,有编译器优化允许在堆栈上分配对象(或避免一起分配)。尤其是逃逸分析在 java 6 中允许这样做。

【讨论】:

    【解决方案2】:

    首先,请记住,堆中还将包含您的类(以及其他几个)的 Class 实例。

    回复:

    ## TestA obj = new TestA()

    STACK - [为 main 函数占用一些内存 -> 这里我们有 b 和对 TestA 的引用]

    HEAP - [为 int a 占用一些内存]

    a 将在堆中,而不是在堆栈中,作为分配给TestA 实例的内存的一部分。 bobj 在堆栈上,在进入 main 时分配(呃,我认为那是发生的时候;可能是 JVM 在遇到程序流,但我们正在进入 JVM 的内部)。堆还包含TestA 的实例。 (请记住,变量obj 与它指向的[TestA 的实例] 完全不同;每一个都占用内存。)

    还请记住,堆栈将包含函数调用的返回地址。比如main调用methodA时,methodA返回时JVM应该返回的地址也在栈上。

    还将分配各种堆栈结构用于异常处理。

    以上内容主要是理论上的,请注意。欢迎 JVM 进行优化,他们确实这样做了(HotSpot 是一个彻底优化的 JVM)。 @Voo 指出,例如,如果 JVM 检测到可以将对象放入堆栈(例如,当对象实例仅在方法中使用并且 JVM 的字节码分析表明不可能当方法退出时成为一个突出的引用)。

    【讨论】:

    • 嗯,所以当我声明一个对象时,它会为方法占用内存吗?是不是在我进入方法的时候需要的(所以当我在obj.methodA(b);的时候)?
    • @MarcoPace:假设你有一个方法,它包含这一行:Foo f = new Foo();。那里发生了两件截然不同的事情:Foo f;f = new Foo();。第一个是堆栈分配;它在进入方法时发生(至少,我相信它会发生;它可能仅在程序流中遇到Foo f 时发生,但我们正在进入 JVM 的内部)。第二个是堆分配,它发生在执行那行代码时。
    • @MarcoPace:注意 Voo 关于 JVM 优化的观点,我已经更新了答案以提及它。
    • @TJ 假设我们谈论的是带有堆栈指针的常用寄存器机器:函数所需的最大空间量在方法入口处分配,相同为所有堆栈变量的总和。如果几个变量的生存时间不重叠,JIT 可以共享它们的空间。 Afaik 字节码本身指定了变量的完整大小(并且解释器不进行共享优化),因为它使调试变得更加困难。
    • @Voo:很好。我添加了一个关于 JVM 优化的后记,因为它们(好吧,无论如何,HotSpot;我唯一有实际经验的)这些天做了非常酷的事情。
    【解决方案3】:

    尽管 Java 被指定为堆栈机器,但实际上并没有以这种方式实现,因此在真正的 JVM 中,堆栈的大小仅在退出或进入方法时发生变化。

    堆永远不会是空的——它包括像Object.class 这样的对象,这些对象在main 启动之前由引导类加载器实例化。

    new ClassName(...) 这样的所有操作都在堆中分配空间*,所有变量声明(int xObject ref)都指定在进入封闭函数时应在堆栈上留出空间**。

    * - 请参阅 https://stackoverflow.com/a/8690592/20394
    中有关优化的警告 ** - 再次优化可能会导致堆栈槽被共享。

    【讨论】:

      猜你喜欢
      • 2012-10-16
      • 2014-06-23
      • 1970-01-01
      • 1970-01-01
      • 2011-03-27
      • 1970-01-01
      • 2015-09-19
      • 2020-01-11
      • 2017-04-28
      相关资源
      最近更新 更多