JVM在运行时将内存划分了五个部分,如图所示:
程序计数器(行号指示器)
定义
- 指向当前线程所执行的字节码指令的地址。
- 可以通过计数器的值来改变下一条需要执行的字节码指令。
特点
- 如果线程正在执行的是Java方法,则这个计数器记录的正是正在执行的虚拟机字节码指令地址。
- 如果正在执行的 Native 方法,则这个计数器的地址位空(undefined)。
- 程序计数器是唯一一个不会产生内存溢出的区域。
程序计数器会随着线程启动而创建,下面我们直观的看一下程序计数器包含哪些信息:
代码如下:Test.java --> Test.class
public class Test {
public int add() {
Integer a = 10;
Integer b = 2;
Integer c = 3;
return a + b + c;
}
}
将代码编译后的 Test.class 文件 使用 javap -c
命令看下 class 文件中的格式:
图中使用红线框起来的就是字节码的偏移地址 ,可以简单理解为class文件的行号。当执行到方法 add() 的时候在当前线程中创建相应的程序计数器,在计数器中存放执行地址 0、2、5、6、7...... 。
这也说明我们程序在运行过程中计数器改变的只是值,而不会随着程序的运行需要更大的空间,也就不会发生内存溢出的情况。
虚拟机栈
定义
描述方法执行的内存模型,在每个方法创建执行的同时都会创建一个栈帧,用于存储 局部变量表、操作数栈、动态链接、方法出口
等信息。每个方法从调用一直到执行完毕,都对应着一个栈帧在VM stack 中 入栈到出栈
的过程。
结构
局部变量表
局部变量表是一组变量的存储空间,用于存放方法参数和方法内部定义的局部变量。其中存放了编译期间可知的基本数据类型、对象类型和方法返回地址类型。
- 基本数据类型:byte、short、int、long、float、double、char、boolean。
- 对象引用类型:
reference
类型,与对象引用不等价。指向对象起始地址的引用指针。 - 方法返回地址类型:
returnAddress
类型指向一条字节码指令地址。
操作数栈
操作数栈是一个后入先出(Last In First Out,LIFO)栈。
操作数栈中存的每个元素可以是任意的java数据类型,用来存放局部变量表中操作变量的值。当一个方法开始执行时,操作数栈是空的,随着方法执行和字节码指令执行,会从局部变量表中或对象实例的字段中复制常量或者变量写到操作数栈中。再随着计算的进行将操作数栈中的元素出栈到局部变量表或者返回给方法调用者。也就是入栈/出栈过程。
动态链接
动态链接也可称为指向运行时常量池的方法引用。在Java源文件被编译到字节码文件时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中,在描述一个方法调用到另一个方法时,就是通过常量池中直向另一方法的符号引用来表示的。动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。常量池中的符号引用在每一次运行期间转换为直接引用即为动态链接。
方法出口
returnAddress。当一个方法开始执行后,会有两种方式退出该方法:正常完成出口
和异常完成出口
。
-
正常完成出口
程序执行过程中遇到任意一个方法返回的字节码指令,返回值传递给上一层调用者,正常退出程序方法。
-
异常完成出口
程序执行过程中遇到异常,这个异常在方法体内没有得到处理(try cache,没有throw异常),导致方法异常退出。
本地方法栈
定义
本地方法栈是位虚拟机使用 Native 方法服务。
本地方法栈结构与原理与虚拟机栈一致,唯一不同的是虚拟机栈中存放的是JDK或者我们自己编写的方法,而本地方法栈中存放的是操作系统底层的方法。
堆
定义
Java堆(Heap)是JVM中最大的一块内存,在JVM启动时就已经创建完毕。是存放对象实例的区域,所有的对象实例及数组都在堆里面进行分配。
堆空间是垃圾回收器(GC)管理的主要区域,简称GC堆。Java堆细分为新生代
和老生代
,新生代中包含了Eden
空间、From Survivor
空间、To Survivor
空间。新生代和老生代在堆中的大小比例为 1:2。
- 新生代、老生代
- 新生代中存放生命周期比较短、占用内存较小的对象。大部分对象都存在新生代中,新生代中内存回收频率高、效率高。
- 老生代中存放生命周期较长、占用内存大的对象。大对象一般是集合、数组、字符串等。
Java堆是被所有线程共享的一块空间,在JVM启动时就已经创建成功了。线程共享可以将Java堆划分出多个线程私有的缓冲区。
Java堆中允许物理空间不连续,只要逻辑连续即可。既可以是固定大小,也可以是扩展(通过 -Xmx
和-Xms
参数进行控制)。
JVM堆内存参数分配
-Xms
: 表示Java虚拟机堆内存初始分配大小。-Xmx
: 表示Java虚拟机堆内存分配最大上限。
在开发过程中通常会将 -Xms
和 -Xmx
两个值设为相同大小。
方法区
定义
方法区用于存储类的元数据
(类的描述信息)、常量池
、方法信息
(方法数据、方法代码)、静态变量、常量以及编译器编译后的代码。和堆一样,方法区也是线程共享区域。
运行时常量池
运行时常量池是方法区的一部分,相比较而言,class 文件中除了有类的版本、字段、方法、接口等描述信息之外,还有一项信息就是常量池,class 文件常量池用于存放编译器生成的各种 字面量("abc")和符号引用,这部分内容将在 类加载后进入方法区的运行时常量池 中存放。
直接内存
导致内存溢出的情况,除了虚拟机中的四个区域(Native Stack、VM Stack、Method Area、Heap)以外,还可能是直接内存。该区域不属于虚拟机运行时数据区部分,也不是Java虚拟机规范中定义的内存区域,但频繁使用会导致内存溢出。
在NIO中会使用到直接内存,由Java虚拟机直接操作操作系统的内存。