持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天
1、Java内存区域
Java运行时数据区的介绍可以参考官方文档对应的章节。摘抄原文如下:
The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
翻译如下:
Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,只有当Java虚拟机退出时才会被销毁。其他数据区域为每个线程。每线程数据区在创建线程时创建,在线程退出时销毁。
1.1、Java虚拟机运行时数据区
java虚拟机运行时数据区,如下图,然后我们介绍一下每个区域的功能作用是,结构特点是什么样的。
1.1.1、程序计数器
Jdk8的虚拟机规范中的关于PC Rigister的描述:The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.
翻译后:Java虚拟机可以同时支持多个执行线程(JLS§17)。每个Java虚拟机线程都有自己的pc(程序计数器)寄存器。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法(§2.6)。如果该方法不是本机方法,则pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果线程当前正在执行的方法是本机的,那么Java虚拟机的pc寄存器的值是未定义的。Java虚拟机的pc寄存器足够宽,可以容纳特定平台上的returnAddress或本机指针。
英文名字叫Program Counter Register,(或许应该叫寄存器更合理一点)是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。作用是字节码解释器工作时通过改变这个寄存器的值来选取下一条需要执行字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个寄存器来完成。
由于Java虚拟机的多线程是通过轮流切换并分配CPU执行时间(时间片)方式来实现的,在任何一个确定的时刻,一个CPU(对于多核心CPU来说就是一个内核)有且只能执行一个线程中的指令。所以为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序寄存器,每个线程之间的寄存器互不影响,独立内存,线程私有,所以PC寄存器是线程私有内存。
注意:如果线程正在执行一个Java方法,这个寄存器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,则这个寄存器值为空(Undifined),此区域在Java虚拟机规范中没有定义任何OutOfMemoryError的区域。
综上,我们大学中学习计算机原理或者操作系统基础知识的时候也有讲到过,CPU也有个PC寄存器,它的作用是记录CPU调度指令的执行现场信息的,两个概念在设计理念是和作用是相似的,作用范围是不同的,CPU的PC指令寄存器调度的是进程,而线程的PC指令寄存器是具体线程中执行的字节码。
1.1.2、Java虚拟机栈
Jdk8的虚拟机规范中的关于虚拟机栈的描述:Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:
- If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a
StackOverflowError. - If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an
OutOfMemoryError.
翻译:每个Java虚拟机线程都有一个私有的Java虚拟机堆栈,与线程同时创建。Java虚拟机堆栈存储帧(§2.6)。Java虚拟机堆栈类似于传统语言(如C)的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为Java虚拟机堆栈除了推送和弹出帧之外从未被直接操作,所以帧可能被堆分配。Java虚拟机堆栈的内存不需要是连续的。
在Java®虚拟机规范的第一版中,Java虚拟机堆栈称为Java堆栈。
此规范允许Java虚拟机堆栈具有固定大小,或者根据计算的需要动态扩展和收缩。如果Java虚拟机堆栈具有固定大小,则可以在创建该堆栈时独立选择每个Java虚拟机栈的大小。
Java虚拟机实现可以为程序员或用户提供对Java虚拟机堆栈初始大小的控制,以及在动态扩展或收缩Java虚拟机栈的情况下,对最大和最小大小的控制。
以下异常情况与Java虚拟机堆栈相关:
如果线程中的计算需要比允许的更大的Java虚拟机堆栈,Java虚拟机将抛出StackOverflowError。
如果可以动态扩展Java虚拟机堆栈,并且尝试进行扩展,但没有足够的内存来实现扩展,或者如果没有足够的可用内存来为新线程创建初始Java虚拟机栈,Java虚拟机将抛出OutOfMemoryError。
以上是官方文档的简单描述,下面我们看看别人怎么说的。
与PC寄存器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型;每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,如下图。每个方法从调用至执行过程的完成,就对应一个栈帧在虚拟机栈中的入栈到出栈的过程。(Slot可以复用,因为并非所有局部变量都在整个执行过程范围内全程被使用)
1.1.2.1、局部变量表
局部变量表(Local Variable Table)是一组变量值的存贮空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中定义了该方法所需要分配的局部变量表的最大容量。
局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应该占用的内存空间大小,只是很有指导性的说每个Slot都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这8中基础数据类型,都可以用32位或更小的物理能存来存放,但这种描述与明确指出“每个Slot占用32位长度的内存空间”是有一些差别的,它允许Slot长度可以随之处理器、操作系统或虚拟机的不同而发生变化。只要保证即使在64位虚拟机中使用了64位的物理能存空间去实现一个Slot,虚拟机仍要使用对齐和补白手段让Slot在外观上看起来和32虚拟机中的一致。
虚拟机通过索引定位的方式使用局部变量,索引值的范围从0开始值局部变量表最大的Slot数量。如果访问的是32位数据类型的变量,索引n代表了使用第n个Slot,如果是64位的数据类型变量,则说明会同时使用n和n+1两个Slot。对于相邻的两个Slot共同存放一个64位的数据,不允许采用任何方式单独访问其中某一个,Java虚拟机规范中明确要求了如果遇到进行这种操作的字节码序列,虚拟机应该在类加载的的校验阶段抛出异常。
在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的实例方法(非静态方法),那局部变量表中第0位索引的Slot默认用于传递方法所属对象示例的引用,在方法中可以通过关键字“this”来访问这个引导韩的参数。其余参数则 按照参数表顺序排列,占用从1开始的局部变量Slot。参数表分配完毕后,再根据方法体内部定义的的变量顺序和作用域分配其其余的Slot。
1.1.2.2、操作数栈
操作数栈(Oprerand Stack),也叫操作栈。它是一个后入先出(Last In First Out LIFO)栈。同局部变量表一样,操作数栈最大的深度也是在编译的时候写到Code属性的max_stacks数据项中。操作数栈的每一个元素都可以是任意的Java数据类型,包括long和double。32位的数据类型所占的栈容量是1,64位数据类型所占的栈容量是2。在方法执行的任何时候,操作数栈的深度都不会超过max_stacks数据项中设置的最大值。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在发发执行的过程中会有各种字节码指令往操作站中写入和提取内容,也就是出栈/入栈操作。例如在做算术运算的时候通过操作数栈来进行的。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近的站定的两个元素已经存入了两个int型数值,当执行这个指令时,会将这两个int值出栈并相加,然后将加的结果入栈。(操作数占中的数据类型必须与字节码整理的序列严格匹配。)
1.1.2.3、动态连接
每个栈帧都包含一个指向运行时常量池中该栈所属的方法的引用,只有这个引用是为了支持方法动用过程中的动态连接(Dynamic Linking)。我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候转化为直接应用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
1.1.2.4、方法返回地址
当一个方法执行开始后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方式称为正常完成出口(Normal Mehtod Invocation Complaetion)。
另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码执行禅城的异常,只要在本方法的异常表中没有搜索到匹配的异常处理,就会导致方法退出,这种退出方法方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给他的上层调用者产生任何返回值的。
无论采用何种方式退出,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC寄存器的值可以作为返回地址,栈帧中可能会保存这个寄存器的值。而方法异常退出时,返回地址要通过异常处理器来确定的。栈帧中一般不会保存这部分信息。
方法退出的过程实际上就等于把当前栈帧出栈,因此退出时可能执行操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作栈中,调用PC寄存器的值一直想方法抵用指令后面的一条指令。
1.1.2.5、附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态链接,方法返回地址与其他附加信息归为一类,称为栈帧信息。
1.1.3、本地方法栈
Jdk8的虚拟机规范中的关于本地方法栈的描述:An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.
The following exceptional conditions are associated with native method stacks:
- If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a
StackOverflowError. - If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an
OutOfMemoryError.
翻译:Java虚拟机的实现可以使用传统堆栈(俗称“C堆栈”)来支持本地方法(用Java编程语言以外的语言编写的方法)。本机方法堆栈也可以由诸如C语言的Java虚拟机指令集的解释器的实现使用。不能加载本机方法并且本身不依赖于传统堆栈的Java虚拟机器实现不需要提供本机方法栈。如果提供,则在创建每个线程时,通常会为每个线程分配本机方法堆栈。
本规范允许本机方法堆栈具有固定大小,或者根据计算需要动态扩展和收缩。如果本机方法堆栈具有固定大小,则可以在创建该堆栈时独立选择每个本机方法栈的大小。
Java虚拟机实现可以为程序员或用户提供对本机方法堆栈初始大小的控制,以及在本机方法栈大小变化的情况下,对最大和最小方法堆栈大小的控制。
以下异常情况与本机方法堆栈相关:
如果线程中的计算需要比允许的更大的本机方法堆栈,Java虚拟机将抛出StackOverflowError。
如果本机方法堆栈可以动态扩展,并且尝试扩展本机方法栈,但内存不足,或者如果内存不足,无法为新线程创建初始本机方法堆,Java虚拟机将抛出OutOfMemoryError。
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用非常相似,它们之间区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中的使用语言,使用方式与数据结构并没有强制的规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(如 sun HotSpot虚拟机)直接将本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
1.1.4、Java堆
Jdk8的虚拟机规范中的关于Java堆的描述:The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
The following exceptional condition is associated with the heap:
- If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an
OutOfMemoryError.
翻译:Java虚拟机具有在所有Java虚拟机线程之间共享的堆。堆是运行时数据区域,从中为所有类实例和数组分配内存。
堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器)回收;对象永远不会被显式释放。Java虚拟机假设没有特定类型的自动存储管理系统,存储管理技术可以根据实现者的系统需求来选择。堆的大小可以是固定的,也可以根据计算需要进行扩展,如果不需要更大的堆,则可以收缩。堆的内存不需要是连续的。
Java虚拟机实现可以为程序员或用户提供对堆初始大小的控制,如果堆可以动态扩展或收缩,还可以提供对最大和最小堆大小的控制。
以下异常情况与堆关联:
如果计算需要的堆超过自动存储管理系统所能提供的堆,Java虚拟机将抛出OutOfMemoryError。
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有的线程共享的一块内存区域,在虚拟机启动创建时创建。此内存区域的唯一目的就是存放对象的实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆。
1.1.5、方法区
Jdk8的虚拟机规范中的关于方法区的描述:The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
- If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an
OutOfMemoryError.
翻译:Java虚拟机有一个在所有Java虚拟机线程之间共享的方法区域。方法区域类似于常规语言编译代码的存储区域,或类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。
方法区域在虚拟机启动时创建。尽管方法区域在逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾收集或压缩。本规范不强制要求方法区域的位置或用于管理已编译代码的策略。方法区域可以具有固定大小,或者可以根据计算的需要进行扩展。方法区域的内存不需要是连续的。
Java虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小变化的情况下,对最大和最小方法区域大小的控制。
以下异常情况与方法区域相关:
如果方法区域中的内存无法满足分配请求,Java虚拟机将抛出OutOfMemoryError。
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。在HotSpot虚拟机上开发的人员很多人称他为“永久代”。
1.1.5.1、运行时常量池
Jdk8的虚拟机规范中的关于常量池的描述:A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file . It contains several kinds of constants, ranging from numeric literals known at compile-time to method and field references that must be resolved at run-time. The run-time constant pool serves a function similar to that of a symbol table for a conventional programming language, although it contains a wider range of data than a typical symbol table.
Each run-time constant pool is allocated from the Java Virtual Machine's method area. The run-time constant pool for a class or interface is constructed when the class or interface is created by the Java Virtual Machine.
The following exceptional condition is associated with the construction of the run-time constant pool for a class or interface:
- When creating a class or interface, if the construction of the run-time constant pool requires more memory than can be made available in the method area of the Java Virtual Machine, the Java Virtual Machine throws an
OutOfMemoryError.
翻译:运行时常量池是类文件中constant_pool表的每类或每接口运行时表示。它包含几种常量,从编译时已知的数字文本到运行时必须解析的方法和字段引用。运行时常量池的功能类似于传统编程语言的符号表,尽管它包含的数据范围比典型的符号表更广。
每个运行时常量池都是从Java虚拟机的方法区域分配的。类或接口的运行时常量池是在Java虚拟机创建类或接口时构建的。
以下异常情况与类或接口的运行时常量池的构造相关:
创建类或接口时,如果构建运行时常量池所需的内存超过Java虚拟机的方法区域中可用的内存,Java虚拟机将抛出OutOfMemoryError。
运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了有累的版本、字段、方法、接口等描述信外,还有一项信息是常量池(Constant Pool Table),用于存放编译器成成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。