运行时数据区

Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干个不同的数据区,这些区域都有自己各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进行的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

JVM运行时数据区如下图所示:

程序计数器

程序计数器是一块较小的内存空间。它可以看作是当前线程所执行的字节码行号指示器。在虚拟机概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。

为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。这块内存为“线程私有内存”。

如果线程在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是native方法,这个计数数器为空。此内存区是唯一一个没有规定任何OutOfMenoryError情况的区域。

Java虚拟机栈

Java虚拟机栈也是线程私有的,它的生命周期与线程的一样。虚拟机栈描述的是方法执行的内存模型。每个方法在调用的时候都会创建一个栈帧,它用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接、方法返回值和异常分派。

栈帧中存放:

局部变量表:存放本方法调用时所用的变量信息

操作数栈:变量运算时所用到的数据结构。

指向当前方法所属的类的运行时常量引用。

方法出口等信息。

本地方法栈

本地方法区为虚拟机使用到native方法服务,虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现。

如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。

如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

Java堆

堆是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在堆在分配内存。随着JIT编译器的发展与逃逸分析技术的成熟,栈上分配、标量替换优化技术会导致一些对象内存分配变化,对象内存分配并不总是在堆上。

Java堆可以处于不连续的内存空间中,只要逻辑上是连续的即可。如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常。

方法区

方法区是线程共享的区域。它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。

方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。方法区在实际内存空间中可以是不连续的。方法区也可不实现垃圾回收算法。如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。

运行时常量池

运行时常量池是方法区的一部分。是每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。运行时常量池具有动态性,运行期间可以将新的常量放入池中。

当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那Java虚拟机将会抛出一个OutOfMemoryError异常。

直接内存

直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范定义的内存区域,这部分内存也会被频繁使用。可能导致OutOfMenoryError异常。

JDK1.4引入NIO类,使用了一种基于通道与缓冲区的I/O方式,它可以使用native函数直接分配堆外内存。然后通过一个存储在Java堆中DirectByteBuffer对象作为这块内存的引用进行操作。可以显著提升性能,因为避免了在Java堆和Native堆中来回复制数据。