Java虚拟机运行时数据区主要包括:程序计数器,本地方法栈,虚拟机栈,方法区,[堆]五部分。每个区域有各自的用途, 创建及销毁时间。其中,方法区及堆这两个区域由所有线程共享。其他几部分为线程私有的数据区,即每个线程都有各自的程序计数器,本地方法栈及虚拟机栈。
程序计数器:
占据一块很小的内存,可以看作当前线程所执行的字节码的行号指示器。计数器记录正在执行的虚拟机字节码指令地址。此区域是JVM中唯一没有规定任何OutOfMemory异常的区域。
物理上程序计数器在CPU内部。
虚拟机栈:
线程私有,生命周期与线程相同。虚拟机栈描述了线程中Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,方法调用过程就是栈帧入栈的过程,执行完成后栈帧出栈。每个栈帧中存储着局部变量表(大小在编译期确定),操作数栈,动态链接及方法出口等信息。启动JVM用-XSS参数指定大小。
此区域规定有两种异常状况:(1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。(2)如果虚拟机栈可以动态扩展, 当扩展时无法申请到足够内存,就会抛出OurOfMemoryError异常。
物理上位于RAM上。
本地方法栈
与虚拟机栈所发挥的作用类似,且一样会抛出StackOverflowError及OutOfMemoryError异常。不同之处在于虚拟机栈为执行Java方法服务, 而本地方法栈则为虚拟机栈使用的Native方法服务。本地方法由C语言实现。
堆
虚拟机所管理内存中最大的一部分,被所有线程共享,在虚拟机启动时创建,用于存放对象实例,几乎所有对象实例及数组均会在堆中被分配内存。说“几乎”是因为今年来因为JIT编译器及逃逸分析技术,栈上分配、标量替换优化技术的使用使得“所有对象及数组都分配在堆上”不那么绝对。
Java堆是垃圾收集器管理的主要区域,因此也称为GC堆。从GC角度来看,Java堆可以细分为:新生代和老年代。进一步还可以分为Eden、From Survivor、To Survivor。
物理上同虚拟机栈一样都位于RAM上。Java堆存储空间只是逻辑上连续,在物理上不连续。可扩展(-Xms, -Xmx)。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OurOfMemoryError异常。
方法区
线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在HotSpot JVM中方法区可细分为持久代和代码缓冲区。在JDK 1.7以后,将原本在永久代中的字符串常量池的位置调到了heap中。在JDK 1.8中持久代被替换为元空间,用以存储类元信息,每一个类加载器的存储区均称为一个元空间。
Java虚拟机规范规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。