模型图
程序计数器
程序计数器是一块很小的内存,可看做当前线程执行字节码的行号指示器,属于线程私有内存。
如果正在执行java方法程序计数器记录的是正在执行虚拟机字节码指令的地址,如果正在执行的是Native方法则值为空(undefined)。
该内存是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemoryError)的。
Java虚拟机栈
Java虚拟机栈也是线程私有,生命周期与线程相同。
虚拟机栈描述的是java执行的内存模型,即每个方法执行的同时都会创建一个栈帧(Stack Frame)来存储局部变量表,操作数栈,动态链接,方法出入口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型(八种基本类型)、对象引用,returnAddress类型(指向了一条字节码指令的地址)。
局部变量表所需要的内存空间在编译期间就完全确定的,在运行期不会改变,64位长度的long和double会占用2个局部变量空间(Slot),其余数据类型只占一个。
在java虚拟机规范中,该区域规定了两种异常:
- 如果线程所请求的栈深度大于虚拟机所允许的深度,则会抛出StackOverflowErroe异常。
- 部分java虚拟机栈深度可动态扩展,java虚拟机规范也允许设置固定值,扩展时无法申请到足够内存,则会抛出OutOfMemoryError异常。
在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。而在多线程环境下,则会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈与虚拟机栈相似,区别在于虚拟机栈执行java方法(字节码)服务,而本地方法栈为虚拟机使用到的Native方法服务。
一些虚拟机(Sun HotSpot虚拟机)直接把虚拟机栈和本地方法栈合二为一。
本地方法栈同虚拟机栈一样也会抛出StackOverflowErroe异常和OutOfMemoryError异常。
Java堆
Java 堆(Java Heap)是虚拟机所管理中的内存最大的一块。
Java 堆是被所有线程共享的一块内存区域。
Java堆唯一的目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
Java堆是垃圾回收器所管理的主要区域,因此简称GC堆(Garbage Collected Heap)。
Java堆可以处于物理上不连续的内存空间中,逻辑上是连续的即可。
Java堆可动态扩展,也可固定大小,如果堆上没有内存完成实例分配,并且堆也无法在扩展时则会抛出OutOfMemoryError异常。
方法区
方法区(Method Area)也是线程共享的内存区域。
方法区用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、静态变量、及时编译器编译后的代码等数据。
Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是还有别名Non-Heap(非堆),应该是与java堆区分开来。
当方法区无法满足内存分配需求时会抛出OutOfMemoryError异常。
运行时常量池
运行时常量池是方法区的一部分。Class文件除了有类的模板、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生产的各种字面量和符合引用,这部分内容将在类加载后进入方法区运行时常量池存放。
一般来说,除了保存Class文件描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量中。
运行时常量池相对于Class常量池还有拧一个特征是具有动态性,java语言并不要求常量一定只有编译器才能产生,运行期也可将新的常量放入池中,例如开发中的String类的intern()方法。
当常量池无法再申请到内存时会抛出OutOfMemoryError异常。