Java虚拟机运行时数据区

677 阅读3分钟

根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存区域共计5项,所属于"线程隔离区域"和"线程共享区域"两个类别。

1. 线程隔离区域

指代所有线程都各自拥有的一块内存区域,不会产生相互之间的影响。

1.1 程序计数器

存储当前线程所执行字节码的行号,是程序实现控制流程(循环、分支、跳转等)的基本保障。

如果当前线程执行的是本地方法(Native) ,则程序计数器的值为空。

这一块内存区域不会抛出OutOfMemoryError

1.2 虚拟机栈

其生命周期与线程保持一致,是描述方法执行的内存模型。

每个方法执行时,都会在虚拟机栈中创建对应的栈帧,方法的调用到执行结束就对应着栈帧的入栈到出栈的过程。

栈帧中有一块区域被称为局部变量表,用于存储编译期可知的基本数据类型、对象引用或ReturnAdress(字节码指令的引用)。其存储空间以slot的方式进行划分,64位的doublelong类型占用2个slot的空间,其余类型占用1个slot的空间。

进入方法时,需要分配多少个slot是在编译后就能够确定的事情,不会在运行时发生变化。

虚拟机栈会在以下两种情况产生错误:

  • 虚拟机栈的深度超过JVM所能允许的最大深度时,产生StackOverflowError
  • 无法申请到足够的内存创建虚拟机栈时,产生OutOfMemoryError

1.3 本地方法栈

其作用与虚拟机栈基本一致,只是其的服务对象是Java本地方法

某些虚拟机在实现层面上,会将虚拟机栈和本地方法栈合二为一(如:Hot-Spot虚拟机)

2. 线程共享区域

所有线程都共享使用的内存区域。

2.1 堆

根据《Java虚拟机规范》中的定义,所有的对象实例和数组都应当在堆上分配

堆内存也是垃圾收集器所管理的区域。由于现代垃圾收集器基本上都是根据分代收集理论设计的,所以常常会认为堆内存中还有"新生代"、"老年代"、"Eden区"、"Survior区"等概念。这个认知是错误的,这些概念都属于垃圾回收器的,而非堆内存本身。

补充概念:为了提升对象分配的效率,堆内存中可能会划分出多个线程私有的分配缓冲区,被称为Thread Local Allocate Buffer, TLAB。但这只是虚拟机在实现层面上的一种优化手法,其无法改变堆内存被所有线程共享的性质。

现代虚拟机几乎都支持"可扩展堆"的设计(通过参数-Xms-Xmx来设置)。当没有足够的内存分配给实例,且堆内存无法再扩展时,将产生OutOfMemoryError

2.2 方法区

存储类型变量、常量、静态变量、即时编译代码缓存等。

在早期的Hot-Spot虚拟机的实现中,将垃圾收集的分代设计扩展至了方法区,已"永久代"的方式实现了方法区,因此也将方法区称为"永久代"。Java8之后,"永久代"被废除,以"元空间"的方式实现了方法区。

运行时常量区是方法区的一部分,用于在类加载后,存储class文件的常量池。

注意:class文件常量池的知识需要参考第六章

方法区无法满足新的内存分配需求时,将会产生OutOfMemoryError

3. 直接内存

在Java1.4引入NIO 后,提供了一种利用DirectByteBuffer来操作堆外内存(直接内存)的方式。

直接内存的分配不受JVM的限制,但肯定会受到服务器本身内存的影响。

若直接内存用量过大,在服务器本身内存不变的情况下,可能会导致JVM可用内存过小,从而易产生OutOfMemoryError