Java运行时数据区
1、程序计数器
程序计数器(Program Counter Register) 是用来记录当前线程执行的指令。JVM执行引擎就是通过改变程序计数器的值来获取下一条需要执行的字节码指令,它是控制程序流程的指示器。
由于一个处理器(核心)在同一时间内,只会执行一个线程中的指令,当执行其他线程时,会发生上下文切换,此时会保存当前线程的上下文。为了线程切换回来后能正常恢复上次的执行状态,每个线程都需要一个独立的程序计数器,独立存储,避免互相干扰。
如果线程正在执行的是一个Java方法,这个计数器记录的正是正在执行的字节码指令地址;如果正在执行的是本地(Native)方法,这个计数器的值则为空(Undefined)。
异常情况:
此区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError(OOM)情况的区域。
2、虚拟机栈
虚拟机栈(VM Stack) 是线程私有,生命周期与线程相同,描述的是Java方法执行的线程内存模型,由一个个栈帧组成。每个方法执行时,JVM都会同步创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。处于虚拟机栈顶的栈帧即当前执行的方法,当方法执行结束后,该栈帧将弹出栈。
异常情况:
①如果线程请求的栈深度大于JVM所允许的深度,将抛出StackOverflowError异常。例如:递归时,未设置正确的退出条件,导致栈深度过大,抛出StackOverflowError。
②如果JVM栈容量可以动态扩展,当栈扩展时无法请求到足够的内存会抛出OutOfMemoryError异常。
HotSpot虚拟机的栈容量是不可以动态扩展的,以前的Classic虚拟机倒是可以。
3、本地方法栈
本地方法栈(Native Method Stack) 与虚拟机栈的作用相似。虚拟机栈是管理Java方法执行的栈,本地方法栈是管理本地(Native)方法执行的栈。
《Java虚拟机规范》对本地方法栈中方法使用的语言、方式与数据结构没有任何强制规定,因此虚拟机可以根据需求自由实现它。
异常情况:
①如果线程请求的栈深度大于所允许的深度,将抛出StackOverflowError异常。例如:递归时,未设置正确的退出条件,导致栈深度过大,抛出StackOverflowError。
②当栈扩展时无法请求到足够的内存会抛出OutOfMemoryError异常。
4、堆
堆(Heap) 是虚拟机是所管理内存中最大的一块,被所有线程共享,生命周期随JVM启动而创建,随JVM关闭而销毁。所有的对象实例以及数组都应当在堆上分配。
现代垃圾收集器大部分都是基于分代收集理论设计的,所以堆往往也被分成不同区域。
①新生代(Eden区、From Survivor / Survivor 0 区,To Survivor / Survivor 1 区)
②老年代(Old区)
异常情况:
①当JVM扩展堆空间时,无法申请到新内存,将抛出OutOfMemoryError。
5、方法区
方法区(Method Area) 与堆一样被所有线程共享。它用于存储JVM已加载的类型信息、常量、静态变量、即时编译器(Just In Time)编译后的代码缓存等数据。
在HotSpot虚拟机上,Java7及以前是使用永久代(Permanent Generation)实现方法区,因此常常把方法区称作永久代,将二者混淆。由于永久代有内存上限,更容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小)。Java8后放弃了永久代,改用元空间(Mete space)实现方法区,并移除-XX:MaxPermSize参数。若在Java8中使用该参数JVM将发出警告(Java HotSpot™ 64-Bit Server VM warning: ignoring option MaxPermSize:256m; support was removed in 8.)。元空间只受进程可用内存上限约束,在当今64位系统中,几乎不会出现内存溢出的问题。
异常情况:
①当方法区扩展空间时,无法申请到新内存,将抛出OutOfMemoryError异常。