Java的内存模型图:
第一、程序计数器(PC)
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条需要执行的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础功能都需要这个计数器来完成。
若线程正在执行的是java方法,PC存储的是的当前线程执行的字节码指令地址,当执行的是本地方法,PC值为空,是唯一一个没有规定OutOfMemoryError情况的区域
注:程序计数器是线程私有的,每条线程都会有一个独立的程序计数器
第二、Java栈(虚拟机栈)
Java栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
注:Java栈也是线程私有的。
异常可能性:对于栈有两种异常情况:如果线程请求的栈深度大于栈所允许的深度,将抛出StackOverflowError异常,如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常
栈帧的概念:
栈帧用于支持虚拟机进行方法调用和执行的数据结构。
1) 局部变量表
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量.局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位. 一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress 8种类型,对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间(long double).
局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
2) 操作数栈
操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈,当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作,例如,在做算术运算的时候通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
举个例子:整数加法的字节码指令add在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int类型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。
3) 方法返回地址
一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用throw字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。 一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的
4) 附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现,这里不再详述。在实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。
第三、本地方法栈
本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。
注:本地方法栈也是线程私有的
异常可能性:和Java栈一样,可能抛出StackOverflowError和OutOfMemeryError异常
第四、Java堆
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例(包括数组)都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。
对堆区进行进一步划分,是为了更好地回收内存或者更快地回收内存。
java堆可以出于物理上不连续的内存空间,只要逻辑上连续即可。
注:堆是线程共享的
异常可能性:如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常
第五、方法区
方法区它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。
注:方法区和堆一样是线程共享的
异常可能性:方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常ja