​ ​ JVM进程所能使用的内存空间包括两大部分,分别是直接内存(Directory Memory;或堆外内存off-heap)、运行时数据区(JVM规范中定义的内存区域)。

​ 了解其所使用的内存分布,以及每个区域的作用、产生的异常等信息,在遇到错误时,可以更加深入的排除问题所在。

运行时数据区(Run-Time Data Areas)

​ JVM进程运行时会使用多种运行时数据区域。

  • 所有数据区域的生命周期和JVM进程的生命周期一致(数据区域创建于JVM进程启动,销毁于JVM进程退出)

  • 有些数据区的生命周期是和JVM进程中的线程生命周期一致(这些数据区域创建于线程创建,销毁于线程退出),因此有些数据区域是线程共享的(thread shared),有些数据区域是线程私有的(thread private)。

Run-Time Data Areas

程序计数器(The pc Register)

​ 程序计数器,专门用来记录当前线程正在执行的JVM指令所在位置。由于JVM进程支持多线程,所以会有线程切换的操作。为了在下次切换到当前线程时,还能从上次切换前的位置继续执行,因此需要保存正确的执行位置。而那些位置数据所存放的地方就是程序计数器中。

​ JVM规范中的信息:

  • 在任何时候,JVM线程都在执行一个方法中的指令,即该线程的当前方法。
  • 如果当前方法不是本地方法(Native Method),则程序计数器会存储正在执行的JVM指令地址。 如果是本地方法,则程序计数器保存值为undefined。

该数据区域没有被规定抛出OutOfMemoryError异常的情况(可能是这块数据区域所需容量少且固定)。

栈( Java Virtual Machine Stacks)

​ JVM栈,JVM线程运行非本地方法时(例如Java方法),所使用的内存空间。该栈中的元素称为帧(frame)。每个栈帧中包含局部变量表、操作数栈、动态连接、方法出口等信息。线程私有。

​ JVM规范中的信息:

  • 由于JVM栈除了push和pop frame外,从不直接操作JVM栈,所以frame可能是由heap分配的。
  • 如果一个线程计算所需的JVM Stack大小超出允许范围,则JVM进程会抛出StackOverflowError异常。
  • 如果一个JVM stack可以动态扩展大小,那么当需要扩展时,内存不够扩展,则JVM进程会抛出OutOfMemoryError异常。

堆(Heap)

​ 堆,包含所有class的实例和数组的分配。所有线程共享的区域。

​ JVM规范中的信息:

  • 存储在堆中的对象由自动存储管理系统回收(即GC,垃圾收集器),对象从不显示的重新分配。

  • JVM假定没有特定类型的自动存储管理系统,因此存储管理技术可以根据实现者的系统需求来选择。

  • 如果一个计算需要的堆内存大小超出自动管理系统所能使用的大小,则JVM进程会抛出OutOfMemoryError异常。

方法区( Method Area)

​ 方法区,用于存放class字节码中的信息。包括class中的常量池、字段、方法和构造方法中的JVM指令等信息。

​ JVM规范中的信息:

  • 虽然方法区在逻辑上是堆的一部分,但简单的实现可以选择不对其进行垃圾回收或压缩。本规范没有规定方法区的位置或用于管理编译后的代码的策略。 (表示JVM进程在实现该内存区域时的位置,更加自由;该区域是否回收内存也可以自由选择)

  • 如果方法区内存不够,则JVM进程会抛出OutOfMemoryError异常。

运行时常量区( Run-Time Constant Pool)

​ 运行时常量区,即在方法区中存放的class字节码的部分数据,包含各种类型的常量,存放class大量信息。

​ JVM规范中定义产生异常的情况如下:

  • 如果当创建一个Class或Interface时,导致方法区内存不够使用,则JVM进程会抛出OutOfMemoryError异常。

本地方法栈( Native Method Stacks)

​ 本地方法栈,也就是给本地方法运行时所用的内存空间。对于Hotspot而言,就是C或C++程序运行方法时, 所用的内存空间。该空间是内存私有的。

​ JVM规范中定义产生异常的情况如下:

  • 如果一个线程使用本地方法时,所需内存空间超出本地方法栈所剩空间大小,则JVM进程会抛出StackOverflowError异常。
  • 如果本地方法栈可以动态扩展。当其尝试扩展时,内存不足;或者在为一个新线程创建新的方法栈时,内存不足。则JVM进程抛出OutOfMemoryError异常。

直接内存(Direct Memeory)

​ 此处的直接内存指的是,JVM进程运行时数据区之外的物理内存。也称之为堆外内存(off-heap)。该内存区域不在JVM规范中定义,但JVM进程也会经常使用。 ​ 比如可以使用UnSafe包中的freeMemory方法直接分配堆外内存,或者使用JDK1.4之后的NIO类,同样可以直接使用堆外内存。

​ JVM规范中定义产生异常的情况如下:

  • 当JVM进程要使用直接内存时,如果此时直接内存的空间不够使用,同样会产生OutOfMemoryError异常。

总结

  • 内存区域关系图

Run-Time Data Areas

  • 内存区域异常关系表
StackOverflowError OutOfMemoryError
The pc Register 0 0
Java Virtual Machine Stacks 1 1
Heap 0 1
Method Area(Run-Time Constant Pool) 0 1
Native Method Stacks 1 1
Direct Memory 0 1

参考资料

《深入理解Java虚拟机》2.2

《The Java® Virtual Machine SpecificationJava SE 8 Edition》2.5