JVM内存管理之运行时数据区域
运行时数据区
概图
运行时数据区包含:
程序计数器,java虚拟机栈,本地方法栈,java堆,方法区
浅谈各数据区域
程序计数器(Program Count Register):
程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。作用是对物理PC寄存器的一种逻辑实现。在字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
java虚拟机多线程是通过线程轮流切换,分配处理器时间实现的,在任何一个确定的时刻,一个处理器都只会执行一个线程的指令(对于多核处理器来说是一个内核)。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各线程计数器互不影响,独立存储。此内存为线程私有的内存。
如果线程执行一个java方法,则计数器所存储的为当前所执行的字节码指令的地址,
如果正在执行本地方法,则计数器所存储为空。
程序计数器内存是唯一一个在java虚拟机规范中没有任何OutOfMemoryError情况的区域。
java虚拟机栈(Java Virtual Machine Stack):
VMS同样也是线程私有的,它的生命周期与线程一致,每个方法被执行时,都会创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应一个栈帧在虚拟机栈从入栈到出栈的过程。
局部变量表存放了编译期可知的各种Java基本数据类型变量,对象引用变量(reference类型,它并不是对象本身,而是一个指向对象本起始地址的引用指针,也可能是指向一条字节码指令的地址)和returnAddress类型(指向了一条字节码指令的地址)。
这些数据类型在局部变量表中的存储空间以局部变量槽(slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余类型变量占用一个变量槽。局部变量表所需内存大小在编译期间完成分配,当进入一个方法时,该方法需要在栈帧中分配的多大的局部变量空间是完全确定的,在方法运行期不会改变局部变量表的大小。
对该区域在java虚拟机规范中规定了两类异常:
- StackOverFlowError
线程请求的栈深度大于虚拟机所规定的栈深度,抛出该异常。 - OutOfMemoryError
如果VMS栈容量可以动态扩展,当栈扩展是无法申请到足够的内存,抛出该异常。
本地方法栈(Native Method Stack):
NMS与VMS功能类似,区别是VMS为VM使用java方法服务,而NMS为VM使用本地方法服务。
java虚拟机规范未对NMS中方法使用的语言,使用方式与数据结构进行强制规定。具体虚拟机可自由实现。
同样的对该区域在java虚拟机规范中规定了两类异常:
StackOverFlowError和OutOfMemoryError
java堆(Heap):
对于java应用程序来说,java heap是虚拟机所管理的最大的一块内存。java heap被所有线程共享,在VM启动时创建。该内存唯一目的就是存放对象实例。
在java虚拟机规范中对java heap的描述为:所有的对象实例以及数组都应当在堆上分配。
随着即时编译技术的发展,尤其是逃逸分析技术的进步,java对象实例在堆上逐渐不是那么绝对了。
java heap是GC所管理的内存区域。因此它也被称为GC Heap。
从分配内存的角度,所有的线程共享的heap可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。
Java Heap可以在物理内存上不连续,但在逻辑内存上必须连续。
Java Heap既可以被实现为固定大小的,也可以是可扩展的,主流的JVM都是按照可扩展来实现的(通过-Xmx和-Xms设定)。
如果在Java堆中没有内存完成实例分配,且堆无法再扩展时,JVM将抛出OutOfMemoryError异常。
方法区(Method Area):
方法区是线程共享的内存区域。它用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。虽然Java虚拟机规范中把方法区描述为堆的一个逻辑部分,但是它却有一个别名称为“非堆”。目的是与Java Heap区分。
因为HotSpot虚拟机使用永久代方法实现方法区,来使得GC能够像管理Java Heap一样来管理方法区,所以在jdk8以前,大部分人愿意将方法区称为永久代,但两者并不等价,永久代仅为方法区的一种实现而已。对于其他虚拟机实现,如BEA JRockit和IBM J9等来说是不存在永久代的。用永久代实现方法区会导致Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰到进程可用内存的上限,就没有问题)。HotSpot在jdk6
时就有放弃永久代改用本地内存来实现方法区的计划了。jdk7将原本放在永久代的字符常量池和静态变量等移出,到jdk8彻底放弃永久代,改用与J9和JRockit一样在本地内存中实现的元空间(Meta-Space)来代替。
java虚拟机规范中对方法区约束宽松,和Java Heap一样不需要连续内存和可选择可扩展或固定内存大小外,还可以选择不实现GC。该内存区域主要是针对常量池的回收和对类型的卸载。
在Java虚拟机规范中,且方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。