Java中的类、对象和引用变量存储在堆(heap)还是栈(stack)中?堆和栈位于何处?

51

我了解方法的变量会被存储在堆栈中,而类变量则会被存储在堆中。那么我们创建的类和对象存储在Java中的哪里?


2
请查看这篇文章,正式定义可以在JVM规范中找到。 - fvu
3
可能令人困惑的是,对象存储在堆上,但在Java中使用的指向该对象的引用可以存储在栈上。同样,对于类对象的引用可以存储在栈上,而类对象本身存储在堆上,但类对象是关于类的元数据,实际代码存储在PermGen中。 - Peter Lawrey
1
@PeterLawrey:PermGen 被认为是堆的一部分,对吗? - Thilo
1
据我所知,它不是最大堆大小的一部分,因此我认为不是。它是一个管理内存空间。 - Peter Lawrey
8个回答

69

JVM中的运行时数据区可以分为以下几个部分:

  1. 方法区:用于存储已编译类文件的存储区域。(每个JVM实例一个)

  2. 堆:用于存储对象的存储区域。(每个JVM实例一个)

  3. Java栈:用于存储本地变量、中间操作结果的存储区域。(每个线程一个)

  4. PC寄存器:用于存储下一条要执行的指令地址,如果下一条指令是本机方法,则PC寄存器中的值将未定义。(每个线程一个)

  5. 本机方法栈:用于执行本机方法(使用Java以外的语言编写的方法)的帮助栈。(每个线程一个)


48
以下是关于Java中内存分配需要考虑的要点:
注意:
对象和对象引用是不同的东西。
1.在Java中,使用很频繁的是`new`关键字来创建一个新对象。但是,`new`所做的是为您正在创建的类的对象分配内存并返回一个引用。这意味着,无论您将对象创建为静态或本地对象,它都会被存储在堆中。
2.所有的类变量原语或对象引用(只是指向对象存储位置的指针,即堆)也存储在堆中。
3.由ClassLoader加载的类、静态变量和静态对象引用存储在堆中的一个特殊位置,即永久代。
4.本地原始变量、本地对象引用和方法参数存储在栈中。
5.本地函数(方法)存储在栈中,但静态函数(方法)存储在永久存储区中。
6.与类相关的所有信息,如类名、与类相关的对象数组、JVM使用的内部对象(如Java/Lang/Object)和优化信息都存储在永久代中。
7.要理解栈、堆、数据,您应该阅读操作系统中的进程和进程控制块。

4
这个回答很有帮助,但是我需要一个参考链接。 - John Red
@mudit_sen - 不确定你所说的“本地函数”是什么意思。如果你在谈论实例方法,那么这些引用必须位于堆上。而且,方法从来不是对象结构的一部分,因为它们本身不是对象。它们存在于代码段上的指令代码中,实例在堆上指向方法的起始地址。 - supi
@supi - 如链接 https://blog.codecentric.de/en/2010/01/the-java-memory-architecture-1-act/ 所述,运行时数据区包含方法区。它有一个解释运行时数据区的老鼠图像。方法代码存在于此方法区中。您所说的“代码段”是什么意思?它是方法区内存的一部分吗?另外,当您说堆上的实例指向方法的起始地址时,这是否意味着HEAP中特定类的所有实例都包含来自方法区的方法起始地址?请澄清一下,谢谢。 - RohitT
@RohitT 代码段是一个通用术语,用于表示指令代码所驻留的区域,例如方法区。在编译过程中,典型的类对象最终将被转换为引导代码,该代码将分配某个地址以及内存到未受管控的堆中,以存储类对象,以及初始化其静态属性和其方法代码所驻留的每个这样的类对象的方法区指令(我从您分享的图表和文章中学习了这些实现细节 - 谢谢)。 - supi
@RohitT 基本上,当中间格式最终转换为机器代码时,整个代码只包含三个部分 - 指令代码(指示CPU执行某些操作),位置(内存位置或寄存器的地址)和数据本身。指令代码通常在程序的生命周期内不会改变,因此它们所在的内存区域或段可以被写保护,而堆栈段则需要写入权限。 - supi
显示剩余2条评论

18
所有Java对象都存储在堆上。持有它们引用的“变量”可以在栈上,也可以包含在其他对象中(这时它们不是真正的变量,而是字段),从而将它们也放在堆上。
定义类的类对象也是堆对象。它们包含组成该类的字节码(从类文件加载)和从其计算得出的元数据。

7

内存的Stack部分包含方法、局部变量和引用变量。

Heap部分包含对象(也可能包含引用变量)。

通过简短的谷歌搜索,我找到了一个描述它的链接,是一个YouTube视频链接。 ^_^

http://www.youtube.com/watch?v=VQ4eZw6eVtQ


1
除了局部变量之外,“堆栈包含方法”是什么意思? - Thilo
5
视频无法观看。你能给我们另一个链接吗? - Maksim Dmitriev
12
该视频已不再可用。下次请考虑在您的回答中概括该视频的内容。这样,如果视频变得不可用,它就不会对您的回答产生太大影响。 - Timothy Zorn
栈中不包含方法。 - user207421
@Kent - 栈部分并不包含方法,只包含当前正在执行的方法所需的数据(如局部变量、临时结果等)。实际的方法指令代码存储在方法区而非栈中。 - supi

5
这个概念非常简单:
  1. 实例变量(原始类型,包装类,引用,对象(非静态)) - 堆
  2. 本地变量,引用 - 栈
  3. 其他数据对象,例如:类元数据,JVM代码,静态变量,静态对象引用,静态函数等,在Java 7之前一直位于Permgen Space中,现在在JAVA 8中被移动到Metaspace。
PS:Metaspace是本地内存的一部分,所以不用担心OOM:Pergem Exeption了。
更多详情请参考:https://siddharthnawani.blogspot.com/

2
根据JVM规范,
类和它自己的常量池,即静态变量存储在方法区。这里的类指的是一堆字段、方法和常量,其中这些方法以指令形式存储在方法区中,并可以通过地址进行标识。
对象只是一个填充好的类模板,在堆内存中创建,但对象引用却在栈中创建。
public class Sample{
int field;
static int constant;
public void test(){
int localVariable=10;
Sample samp=new Sample();
  }
}

在这个例子中,sample.class将进入方法区,这意味着'field'、'constant'和方法'test'都被分配在方法区。 当执行开始时,由new Sample()创建的对象将进入堆,但'samp'只是一个对象引用,它进入堆栈并保存对象在堆中的地址。 更多信息请查看此链接,JVM规范。

"field" 将被存储在堆中,而不是方法区域,因为它既不是运行时常量也不是静态变量。 - amarnath harish

2

局部变量(方法变量)和方法存在于栈上,而对象及其实例变量则存在于堆上。

现在,引用变量可以是局部变量(如果在方法内创建),也可以是实例变量(如果在类内但在方法外创建)。因此,引用变量可以存在于任何地方,无论是栈还是堆。


方法不存储在堆栈中。 - user207421

0
在Java中,变量可以保存一个对象引用(例如在String s="OOO"中,s保存对对象"OOO"的引用)或者基本值(例如在int i=10中,i保存10)。这些变量可以在堆上或栈上,取决于它们是否为局部变量以及使用的JVM版本。
在Java虚拟机8规范文档(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html)中,与此相关的内存组织如下。
每个Java线程都有自己的JVM堆栈。它保存局部变量和部分结果,并在方法调用和返回中发挥作用。堆栈中也可能有对象引用。只要是本地变量,规范不区分原始变量和引用变量。该堆栈使用称为帧的数据单元。然而,由于Java虚拟机堆栈除了推入和弹出帧之外从未直接操作,因此帧可以在堆上分配。因此,JVM规范的实现(如OpenJDK或Oracle)决定帧的位置。
每个JVM实例都有一个堆。堆是运行时数据区,用于分配所有类实例和数组的内存。实际保存对象数据的内存位于堆上。堆在所有JVM线程之间共享。这也包括对象引用,它们是对象的一部分,即实例变量。
例如,考虑以下类:
class Moo{ private String string= "hello"; } class Spoo{ private Moo instanceReferenceVariable = new Moo(); public void poo(){ int localPrimitiveVariable=12; Set localReferenceVariable = new HashSet(); } }
这里被变量instanceReferenceVariable引用的对象将位于堆上,因此该对象的所有实例变量,如string,也将位于堆上。变量localPrimitiveVariable和localReferenceVariable将位于堆栈上。
方法区:方法区是堆的受限部分,其中存储每个类的结构,例如运行时常量池、字段和方法数据以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法。
运行时常量池:这是方法区的一部分。

希望这能澄清事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接