JVM运行时数据区(Run-Time Data Areas) - Java内存分配

译文链接
Java虚拟机(JVM)定义了在程序执行期间使用的各种运行时数据区域。其中一些JVM数据区域是按线程创建的,而其他JVM数据区域是在JVM启动时创建的,而内存区域是在线程之间共享的。

根据使用情况,JVM运行时数据区域可分为六个区域

  • 程序计数器(PC)寄存器
  • Java虚拟机(JVM)堆栈
  • 本机方法堆栈
  • 方法区域
  • 运行时常量池

如上所述,这些记忆区域可分为两类 -

每个线程创建 - PC寄存器,JVM堆栈,本机方法堆栈
由线程共享 - 堆,方法区域,运行时常量池

JVM运行时数据区(Run-Time Data Areas) - Java内存分配

JVM运行时数据区

1.程序计数器(PC)寄存器 - 在任何给定时间的JVM中,许多线程可能正在执行。每个执行线程都有自己的PC寄存器。
如果JVM线程执行的方法是JAVA方法,则PC寄存器包含当前正在执行的Java虚拟机指令的地址。如果线程正在执行本机方法,则Java虚拟机的pc寄存器的值是未定义的。

2.Java虚拟机(JVM)堆栈 - 每个JVM线程都有自己的JVM堆栈,该堆栈在线程启动时创建。JVM堆栈存储从堆栈中推出和弹出的帧,永远不会直接操作JVM堆栈。
在任何异常时,您将获得此堆栈跟踪,其中每个元素表示单个堆栈帧。

与Java虚拟机堆栈相关的特殊条件:
如果线程中的计算需要比允许的更大的Java虚拟机堆栈,则Java虚拟机会抛出*Error。
如果可以动态扩展Java虚拟机堆栈,并且尝试进行扩展但可以使内存不足以实现扩展,或者可以使内存不足以为新线程创建初始Java虚拟机堆栈,则Java Virtual机器抛出OutOfMemoryError。

JVM堆栈中的帧
调用方法时会创建一个新帧,然后将该帧推送到该线程的JVM堆栈中。当方法调用完成时,框架将被销毁。
每个帧都有自己的局部变量数组,它自己的操作数堆栈以及对当前方法类的运行时常量池的引用。局部变量数组和操作数堆栈的大小在编译时确定,并与用于与帧相关联的方法的代码一起提供。

在任何时候只有一个帧是活动的,这是执行方法的帧。该帧被称为当前帧,并且其方法被称为当前方法。定义当前方法的类是当前类。

请注意,由线程创建的帧对于该线程是本地的,并且不能被任何其他线程引用。

  • 局部变量 - 创建并添加到JVM堆栈的每个帧都包含一个称为局部变量的变量数组。
    局部变量数组的长度在编译时自身确定,并以类或接口的二进制表示形式提供,同时提供与帧相关的方法的代码。
    调用方法时,JVM使用局部变量传递参数。
    如果是类方法,则从局部变量0开始,在连续的局部变量中传递任何参数。
    如果它是一个实例方法,则局部变量0总是用于传递对调用实例方法的对象的引用,即this。随后,任何参数都在从局部变量1开始的连续局部变量中传递。

  • 操作数堆栈 - 每个帧包含一个后进先出(LIFO)堆栈,称为帧的操作数堆栈。操作数堆栈的最大深度称为编译时间本身,并随与帧相关的方法的代码一起提供。
    操作数堆栈是方法执行时的实际存储位置。为方法创建帧时,其操作数堆栈为空。Java虚拟机将提供指令,以将局部变量或字段中的常量或值加载到操作数堆栈中。其他JVM指令从操作数堆栈中获取操作数,对它们进行操作,并将结果推回操作数堆栈。
    操作数堆栈还用于准备要传递给方法和接收方法结果的参数。

  • 执行动态链接 - 在已编译的.class文件中,方法的代码是指要调用的方法和要通过符号引用访问的变量。这些符号方法引用通过动态链接转换为具体方法引用,根据需要加载类以解析符号那时尚未定义。
    JVM堆栈中的每个帧都包含对当前方法类型的运行时常量池的引用,以支持方法代码的动态链接。

3.本机方法堆栈 - JVM也可以使用传统堆栈以支持本机方法。本机方法是用Java编程语言以外的语言编写的方法。
在创建每个线程时,每个线程都会分配本机方法堆栈。

以下异常条件与本机方法堆栈相关联:

如果线程中的计算需要比允许的更大的本机方法堆栈,则Java虚拟机会抛出*Error。
如果可以动态扩展本机方法堆栈并尝试本机方法堆栈扩展但可用内存不足,或者如果可用内存不足以为新线程创建初始本机方法堆栈,则Java虚拟机会抛出OutOfMemoryError。
4.
堆是JVM运行时数据区,内存从该数据区分配给对象,实例变量和数组。堆是在JVM启动时创建的,并在所有Java虚拟机线程之间共享。
一旦存储在堆上的对象没有任何引用,该对象的内存就由垃圾收集器回收,垃圾收集器 是一个自动存储管理系统。永远不会明确释放对象。

以下异常情况与堆相关联:

如果计算需要的堆数超过自动存储管理系统可用的堆,则Java虚拟机会抛出OutOfMemoryError。
请参阅Java中的堆内存分配以了解有关堆内存分配以及如何在此处收集垃圾的更多信息

5.方法区域 - JVM具有在所有JVM线程之间共享的方法区域。方法区域存储有关已加载的类和接口的元数据。它存储每类结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码。
存储在方法区域中的类型信息由JVM加载的每种类型如下 -

  • 类/接口的完全限定名称。
  • 任何直接超类的完全限定名称。
  • 使用的修饰符。
  • 任何扩展超级接口的完全限定名称。
  • 用于区分加载类型是类还是接口的信息。

除了类型信息方法区域还存储

  • 运行时常量池
  • 字段信息,包括字段名称,类型,修饰符。
  • 方法信息,包括方法名称,修饰符,返回类型,参数。
  • 静态(类)变量。
  • 方法代码包含字节代码,局部变量大小,操作数堆栈大小。

方法区域通常是非堆内存的一部分,以前称为PermGen空间。请注意, PermGen Space从Java 8更改为MetaSpace。

请参阅Java 8中的PermGen Space Removal以了解有关Java 8中MetaSpace的更多信息。
以下异常条件与方法区域相关联:

如果无法使方法区域中的内存满足分配请求,则Java虚拟机将抛出OutOfMemoryError。
6. 运行时常量池
运行时常量池是类的constant_pool表的每类或每个接口存储。Constant_pool包含在编译时已知的常量(字符串文字,数字文字),它还存储必须在运行时解析的方法和字段引用。
运行时常量池在线程之间共享,并从JVM的方法区域分配。

不是将所有内容存储在字节代码中,而是为类维护单独的常量池,字节代码包含对常量池的引用。这些符号引用通过动态链接转化为具体的方法参考。

例如 - 这是一个字节代码片段 -

0 :aload_0
1 :invokespecial #1 //方法java / lang / Object。“”????)V 4 :aload_0
5 :new #2 // class javafx / beans / property / SimpleStringProperty

如果你注意到这里invokespecial operand有一个前缀#1,这个数字(#1)引用存储实例初始化方法的常量池。

第5行中的新Opcode后面跟着编号#2。这意味着引用常量池中的第二个索引。

以下异常条件与类或接口的运行时常量池的构造相关联:

创建类或接口时,如果运行时常量池的构造需要的内存比Java虚拟机的方法区域中可用的内存多,则Java虚拟机会抛出OutOfMemoryError。