五、Jvm系列(1)——内存区域
1. Java 虚拟机运行时数据区域
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
2. 各区域解释
详细模型图:
2.1 程序计数器
说明:
程序计数器是一块较小的内存空间,它可以看作当前线程所执行的字节码的行号指示器。
由于 Java 虚拟机的多线程是通过 线程 轮流切换 并 分配处理器执行时间 的方式来实现的。在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为 “线程私有” 的内存。
如果线程执行的是一个 Java 方法,这个计数器记录的是正在执行的 虚拟机字节码指令的 地址。
如果线程执行的是一个 Native 方法,这个计数器值则为 空(Undefined)
异常:
- 此内存区域是唯一 一个在 Java 虚拟机规范中 没有规定任何 OutOfMemoryError 情况的区域。
2.2 虚拟机栈
说明:
虚拟机栈 (Java Virtual Machine Stacks) 的生命周期与线程相同。
虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个 栈帧 (Stack Frame)。
栈帧:是方法运行时的基础数据结构。栈帧包括
局部变量表 : 所需的内存空间在编译期间完成分配,存放了编译器可知的各种基本数据类型、对象引用和 returnAddress类型
操作数栈
动态链接
方法返回地址
异常:
StackOverflowError: 当线程请求的 栈深度 大于虚拟机所允许的深度。
OutOfMemoryError: 当前大部分的 Java 虚拟机都可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出该异常。
2.3 本地方法栈
说明:
与虚拟机栈所发挥的作用是非常相似的
虚拟机栈为虚拟机执行 Java 方法服务
本地方法栈为虚拟机执行 Native 方法服务
异常:
与虚拟机栈一样
StackOverflowError
OutOfMemoryError
2.4 堆
说明:
在虚拟机启动时 创建。
此内存区域的唯一目的就是 存放 对象实例,几乎所有的对象实例都在这里分配内存。 但是也有例外,随着JIT编译器的发展与逃逸技术逐渐成熟,栈上分配、标量替换优化技术会导致部分对象实例不分配在堆上,而是栈内存。
Java 堆是垃圾收集器管理的 主要区域,因此很多时候也被叫做 GC堆。
从 内存回收 角度:
- Java 堆可以细分为:新生代和老年代;这样划分的目的是为了更好的回收内存。
从 内存分配 角度:
- 线程共享的 Java堆 中可能划分出多个线程私有的 分配缓冲区(Thread Local Allocation Buffer, TLAB);这样划分的目的是为了更好的分配内存。
异常:
- OutOfMemoryError:如果在堆中没有 足够的内存 完成 实例分配,并且 堆 也无法再扩展时,将会抛出 OutOfMemoryError异常。
2.5 方法区(永久代)
说明:
用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。
Java虚拟机规范对方法区的限制非常宽松,方法区可以选择不实现垃圾收集。垃圾收集行为在这一区域是比较少出现的。
该区域内存回收目标是针对 常量池的回收 和 对类型的卸载。
异常:
- OutOfMemoryError: 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
2.6 运行时常量池
说明:
运行时常量池(Runtime Constant Pool) 是方法区的一部分。
用于存放编译器生成的各种 字面量 和 符号引用。
运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较常见的是String类的intern()方法。
异常:
- OutOfMemoryError:既然是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时将抛出 OutOfMemoryError 异常。