五、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 异常。