运行时数据区域
程序计数器
概念: 程序计数器是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等都依赖程序计数器完成。
特性: 每个线程独一份,各线程之间的程序计数器互不影响,独立存储,即线程私有。
注意:
- 如果当前线程执行的是Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果当前线程正在执行的是本地方法(Native),则计数器值为空(Undefined)。
- 程序计数器不会内存溢出。
Java虚拟机栈
概念: Java方法执行的线程内存模型;每个方法被执行的时候都会同步创建一个栈帧用于存放局部变量表、操作数栈、动态连接、方法出口等信息。每个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
特性:
- 线程私有
- 每个栈帧的内部结构
- 局部变量表存放了编译期间的基本数据类型(boolean、byte、char、short、int、long、float、double)、对象引用(指向对象的引用指针)和returnAddress类型(指向一条字节码指令的地址)
- 操作数栈:主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
- 动态链接:指向运行时常量池的方法引用。
- 方法返回地址:用来存放调用该方法的 PC 寄存器的值。(退出方法后的程序位置)
- 附加信息
注意:
8. 线程请求的深度大于虚拟机所允许的深度,则抛出StackOverflowError异常
9. 如果虚拟机栈内存可动态扩展,当栈扩展无法申请到足够内存,则抛出OutOfMemoryError异常
本地方法栈
概念: 其他语言编写的、被Native修饰的本地方法。
堆
概念: 存放对象和数组
特性:
- 线程共享
- 虚拟机启动时创建
- 为了更好的分配和回收内存,Java堆可以划分出多个线程私有的分配缓冲区(TLAB)以提升效率
- Java堆可以处于物理上不连续的内存空间中(逻辑上应被视为连续),大对象(如数组)为追求高效一般会要求连续的内存空间。
- 堆内存可固定,也可扩展(-Xmx和-Xms),无法扩展时会抛OutOfMemoryError异常。
方法区
概念: 存储虚拟机加载的类型信息、常量、静态常量、即时编译器编译后的代码缓存等数据。
特性: 线程共享。
注意:
- 方法区时一个概念,不同虚拟机或不同版本对其实现不同。
- JDK1.6及以前,方法区时通过堆内存中的永久代来实现的,初衷是为了能够像管理堆一样管理方法区。这很容易导致堆内存溢出;JDK1.7时,将字符串常量和静态常量等移植到了Java堆(不是永久代);JDK1.8以后,完全放弃了永久代的概念,通过在本地内存中实现的元空间来代替。
运行时常量池
概念: 是方法区的一部分,用于存放编译期间生成的各种字面量和符号引用(常量池表)
注意:
- 具备动态性,常量不要求编译期才能产生,运行期间产生的常量也能被放入池中。(String :: intern())
- 常量池无法再申请到内存时会抛OutOfMemoryError
直接内存
概念: JDK1.4引入了NIO类,一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
注意: 直接内存不是虚拟机运行时区域的一部分,但也可能导致OutOfMemoryError异常。受本机总内存大小限制,当每个内存区域总和大于物理内存时,直接内存动态扩展会导致OutOfMemoryError。