JVM笔记03.Runtime Data Area

200 阅读4分钟

本文主要基于Java SE 8 版本的Java虚拟机规范和周志明老师的《深入理解Java虚拟机》。

[toc]

JVM Runtime Data Area

JVM定义了若干种程序运行期间会使用到的运行时数据区域Runtime Data Area,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

程序计数寄存器 The pc Register

每一条JVM线程都有自己的pc(Program Counter)寄存器。程序计数器记录当前线程正在执行的字节码指令命令地址(ReturnAddress类型),如果执行Native方法,这个计数值则为未定义的(Undefined)。程序计数寄存器是Java虚拟机规范中唯一规定不会出现任何异常情况的运行时数据区域。

Java虚拟机栈 Java Virtual Machine Stacks

  • 虚拟机栈也是线程私有的,它的生命周期与线程相同。
  • 虚拟机栈描述的是Java方法执行的内存模型。
  • 每个方法在执行的同时都会创建一个栈帧。
    • 栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 方法执行调用过程,就是栈帧在虚拟机栈中的入栈、出栈过程。
  • 规范规定了这个区域的两种异常情况。
    • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
    • 如果可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

堆 Heap

  • Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。
  • 唯一目的就是存在对象实例。
  • 如果堆中沒有内存完成实例分配,并且堆也无法再扩展时,将会抛出OOM异常。

方法区 Method Area

  • 方法区也是所有线程共享的一块内存区域。
  • 它用于存储已被虚拟机加载的类信息、常量、静态常量、JIT编译后的代码等数据。
  • 如果方法区沒有内存完成分配,将会抛出OOM异常。

PernGem和Metaspace

PernGem和Metaspace都是方法区的实现。

  • PernGem (<1.8)
    • 字符串常量位于PernGem
    • FGC不会清理
    • 大小启动的时候指定,不能变
  • Metaspace (>=1.8)
    • 字符串常量位于堆
    • 会触发FGC清理
    • 不设定的话,最大就是物理内存
为什么弃用PernGem
  • 字符串存在永久代中,容易出现性能问题和内存溢出
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
  • 将 HotSpot 与 JRockit 合二为一

Run-Time Constant Pool 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

本地方法栈 Native Method Stacks

  • 存放native方法的栈帧,与底层硬件方法接触,不需要深究。
  • 和虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

直接内存/堆外内存

使用未公开的Unsafe和NIO包下ByteBuffer来创建堆外内存。在配置虚拟机参数时,需要注意堆外内存的存在,以免各个内存区域总和大于物理内存限制,从而导致OOM异常。

栈帧 Frames

指令集架构有两种设计,一种是基于栈的指令集,另一种则是基于寄存器的指令集。前者是JVM采取的设计,后者是cpu采取的设计。

JVM以方法作为最基本的执行单元,栈帧则是用于支持jvm进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。

局部变量表 Local Variables

用于存放方法参数和方法内部定义的局部变量的存储空间。在Java程序被编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。

操作数栈 Operand Stacks

操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。

动态链接 Dynamic Linking

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

方法调用正常完成 Normal Method Invocation Completion

执行到return指令时,方法调用正常完成。

方法调用异常完成 Abrupt Method Invocation Completion

执行到athrow指令或发生异常,且没捕获处理,方法调用异常完成。