这篇博客主要讲一下JVM的内存模型。话不多说先上一张概览图:
JVM内存模型
下面我们主要通过作用线程私有情况异常情况补充说明这四个情况来介绍和说明各个内存区域的情况。

1、程序计数器:

  • 作用:程序计数器可以当做当前线程执行的字节码指示器,记录当前执行到那一条指令字节码解释器在工作时就是通过改变这个计数器的值选取下一条需要执行的字节码指令,分支、循环、跳转等基础功能都需要依靠这个计数器来完成。
  • 线程私有情况:线程私有,因为Java虚拟机的多线程机制是通过抢占时间片的形式来实现的,就是说在一个确定的时刻,一个处理器只会处理一个线程,那么这就涉及到线程恢复的问题,比如某个线程的执行完第n条指令后,失去了对处理的占用,为了在它抢占到处理器时可以恢复到正确的执行位置(即第n+1条指令处),每个线程必须维护一个独立的程序计数器,不允许被其他线程篡改。所以程序计数器必须是线程私有的。
  • 异常情况:它是JAVA虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域
  • 补充说明:如果当前线程执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果是执行一个Native(本地)方法,这个计数器则为空(Undefind)。

2、Java虚拟机栈

  • 作用:在每个方法执行的时候会创建一个帧栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用的执行结束的过程就对应一个帧栈在虚拟机栈中入栈到出栈的过程。
  • 线程私有情况:线程私有。
  • 异常情况:有两种异常:1、当线程请求的栈的深度大于虚拟机栈所允许的深度,将会抛出StackOverflowError异常。2、如果虚拟机栈可以动态拓展,如果在动态拓展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
  • 补充说明:局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法所需要的内存大小是完全确定的。在方法运行期间不会改变局部变量表的大小。

3、Java堆

  • 作用:存放对象实例。
  • 线程私有情况:线程共享。因为所有线程创建的对象实例几乎都在Java堆上分配内存,所以必然要线程共享。
  • 异常情况:Java堆可以通过-Xms和-Xmx来分配Java堆的大小,当堆中没有内存完成实例分配,而且堆中也无法拓展时,将会抛出OutOfMemoryError异常。
  • 补充说明:随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将导致一些变化的发生,将不会把所有的对象都分配在对象上了。

4、方法区

  • 作用:用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器后的代码等数据。
  • 线程私有情况:线程共享。
  • 异常情况:当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
  • 补充说明:Java虚拟机对方法区的实现要求比较宽松,可以实现垃圾回收也可以不实现垃圾回收,因为这里垃圾回收的效率非常低,但有些事实表明这里实现垃圾回收是十分必要的。

5、运行时常量池

  • 作用:运行时常量池时方法区的一部分,在Class文件中有专门一个字段来描述这个部分,用于存放编译区生成的各种字面量和符号引用。
  • 线程私有情况:线程共享
  • 异常情况:常量池无法申请到内存时抛出OutOfMemoryError异常。
  • 补充说明:常量池中常量不一定只在编译器产生,运行期也可以将新的常量加入常量池中。典型的例子的是:String类的intern()方法。

6、直接内存

  • 作用:从某种程度上说这并不是JVM的内存区域。但是Java程序可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。=,可以避免在Java堆和Native堆中来回数据,可以显著提高性能。
  • 线程私有情况:虚拟机外内存,线程共享。
  • 异常情况:受到本机物理内存的限制,当无法满足内存分配需求的时候会抛出OutOfMemoryError异常。
  • 补充说明:直接内存不是Java虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分内存也被频繁的使用。