参考《深入理解java虚拟机:JVM高级特性与最佳实践(第三版)》
参考https://www.cnblogs.com/noteless/p/9510252.html
jvm运行时数据区域
JVM在执行java程序过程中会将其管理的内存划分为若干个不同的数据区域,如下图所示:
各区抛出异常情况
程序计数器
可以看作是当前线程做执行的字节码的行号指示器。在JVM概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,他是程序控制流的指示器。程序计数器是线程私有的,可以用于上下文切换。如果线程正在执行一个java方法,这个计数器记录的是正在执行的JVM字节码指令的地址;如果正在执行native方法,计数器为空。此内存区域是唯一一个在《JVM规范》中没有规定任何outOfMemoryError情况的区域。
虚拟机栈
虚拟机栈是线程私有的,生命周期和线程相同。虚拟机栈描述的是java方法执行的线程内存模型:每个方法执行时,jvm会同步创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法被调用直到执行完毕的过程对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型(int,long等)、对象引用和returnAddress类型。这写数据类型在局部变量表中的存储空间以局部变量槽表示,其中long\double占用两个槽,其他数据类型占用一个槽。局部变量表所需的空间在编译期间完成分配,不同虚拟机的变量槽的内存空间可能不一样(一个变量32比特或者64比特)。
如果线程请求的栈深度超过了虚拟机允许的深度时会抛出StackOverflowError异常;如果jvm栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常(HopSpot不允许栈空间扩展,只有在申请的时候空间不足会抛出该异常,在运行的时候不会出现该异常)。
本地方法栈
和虚拟机栈类似,只不过本地方法栈服务于本地方法(native方法)。也会抛出StackOverflowError异常和OutOfMemoryError异常。
java堆
java堆是虚拟机所管理内存中最大的一块,被所有线程共享,唯一作用是存放对象,几乎所有的对象实例及数组都分配在java堆上。基于经典分代GC设计的JVM中一般将java堆分成新生代、老年代、永久代。不过当前也出现了不采用分代GC的收集器。
java堆可以处于不连续的内存空间上,但是出于实现简单、存储高效的考虑,一般对于大对象要求内存连续。
可以通过-Xmx -Xms设定堆大小,当堆内存超过上限时会抛出OutOfMemoryError异常。
方法区
方法区被各个线程共享,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
jdk8以前,Hotspot设计团队把收集器的分代设计扩展至方法区或者说用永久代实现了方法区,所以有的人会认为方法区是永久代,实际上是不严谨的。jdk8将方法区采用本地内存来实现,这个空间叫做元空间(meta-space),方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池
Class文件中除了有类版本、字段、方法、接口等信息的描述外,还有一项信息是常量池表,用于存放编译期间生成的各种字面量和符号引用,常量池表在类加载后存放到方法区的运行时常量池中。运行时常量池一般除了保存class文件中描述的符号引用外,还会把符号引用翻译出来的直接引用也存储在运行时常量池中。
另外运行时常量池具备动态性,处理类加载时导入的常量外,可以在程序运行中加入新的常量。常量池无法再申请内存的时候会抛出OutOfMemoryError异常。
直接内存
直接内存不是JVM运行时数据区的一部分,也不是《规范》中定义的内存区域,但是这部分可能被频繁使用,而且也会导致OutOfMemoryError异常。