JVM的内存模型

JVM所管理的内存分为一下几个运行时数据区:

  • 程序计数器
  • java虚拟机栈
  • 本地方法栈
  • java堆
  • 方法区

程序计数器(Program Counter Register)

一块比较小的内存空间,它是当前线程所执行的字节码的行号指示器,通过该计数器的值来确定下一条需要执行的字节码指令,分支、跳转、循环等都要依赖它来实现。每条线程都有一个独立的程序计数器,各个线程之间的计数器相互独立、互不影响,该区域是线程私有的。

当线程在执行一个java方法是,该计数器记录的是正在执行的虚拟机字节码指令地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器值为空。

该内存区域是唯一一个在java虚拟机中没有规定任何OOM(out of memory)情况的区域

java虚拟机栈(Java Virtual Machine Stacks)

该区域也是线程私有的,它的生命周期和线程相同。

虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧,栈它用于支持虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

栈帧用于存储局部变量、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

在编译代码时,栈帧需要多大的局部变量表、多深的操作数栈都已经确定了,并且写入了方法表的code属性中。因此,一个栈帧需要分配多少内存,不会受到程序运行期标量数据的影响,而取决于具体虚拟机的实现。

在Java虚拟机规范中,对这个区域规定了两种异常情况:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
  2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemory异常。

本地方法栈(Native Method Stacks)

该区域与虚拟机栈发挥的作用非常相似。

虚拟机栈为虚拟机执行java方法服务,本地方法栈为使用到本地操作系统(Native)方法服务。

java堆 (java heap)

java heap是java虚拟机所管理内存中最大的一块,是所有线程共享的一块内存。几乎所有的对象实例和数组都在这里分配内存。java heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。

根据java虚拟机规范规定,java堆可以处在物理上不连续的内存空间,只要逻辑上连续即可。如果在堆中没有内存可分配、而且堆也无法扩展时,将会抛出OutOfMemeryError异常。

方法区(Method Area)

方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区域又被称为“永久代”,但这仅仅对于Sun HotSpot来讲,JRockit和IBM J9虚拟机中并不岑仔永久代这样的概念。

java虚拟机规范把方法区表述为java堆的一个逻辑部分,而且它和java heap一样不需要实现连续内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域很少出现。该区域的内存回收目标主要是针对废弃常量和无用类的回收。

运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在加载后存放到方法区的运行时常量池中。运行时常量池相对与Class文件常量池的另一个重要特征是具备动态性,java语言并不要求常量一定只能在编译期产生,也就是非预置入class文件的常量池的内容才能进入方法区的运行时常量池,运行其间也可能将新的常量放入池中。这种特性被开发人员利用较多的是String类的intern() 方法。

根据虚拟机规范的规定,当发放区无法满足内存分配的时候,将抛出OutOfMemoryError异常