JVM在运行Java代码时,会把内存分为几个模块即数据区来使用,数据区的内容如下图所示:
1. PC寄存器:
JVM支持程序多线程执行。而操作系统的任务调度采用的是时间片轮询的抢占式调度方式,也就是说,某一个确定的时刻,一个处理器只能处理一条线程中的指令。因此,线程切换后如何恢复到原来正确的位置,便是通过每个线程各自的寄存器来实现的。在各线程中寄存器独立,互不干扰。
如果线程执行的方法不是native的,那PC寄存器保存的就是Java虚拟机正在执行的字节码指令的地址,如果该方法是native的,那寄存器的值就是undefined(null),此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。2. JAVA虚拟机栈:
java虚拟机栈也是Java线程私有的,它的生命周期与线程一样,用于存储栈帧,主要包括两部分,方法中的局部变量和方法执行过程中产生的中间结果。每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。因为除了栈帧的出栈和入栈之外,Java 虚拟机栈不会再受其他因素的影响,所以栈帧可以在堆中分配,Java 虚拟机栈所使用的内存不需要保证是连续的。
JVM规范允许VM Stack要么是一个固定大小,要么动态扩展来满足要求。如果JVM栈是一个固定的大小,当栈被创建的时候每一个栈大小可以自由设置。而在动态扩展情况下,可以控制最大或最小内存;
Java虚拟机栈可能会发生如下异常情况:
- 如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异常;
- 如果 Java 虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常;
3. Java堆
对于大多数应用来说,堆是Java虚拟机所管理的内存中最大的一块。堆是可供所有线程共享的一块内存区域,是在虚拟机启动的时候创建的。它唯一的目的就是存储对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾回收器管理的主要区域,很多时候也被翻译为GC堆。而从内存分配的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代,老年代,永久代等等。Java堆的容量可以是固定大小的,也可以根据需求动态扩展,并且Java堆可以处于物理上不连续的内存空间中。只要逻辑上是连续的即可,就像我们的磁盘空间一样。堆中可能会发生的异常:- 如果实际所需的堆大小超过了系统设置的最大堆容量,那JVM会抛出OutOfMemoryError异常;
4. 方法区
方法区与堆一样,是各个线程共享的内存区域,存储了每一个类的结构信息,如运行时常量池,静态变量,常量,构造方法和普通方法的字节码内容等等;方法区也是在虚拟机启动的时候被创建。 JVM规范堆方法区的限制非常宽松,它可以作为堆的一个逻辑组成部分,拥有和堆大部分相同的性质,如可以选择固定大小和可扩展,也可以选择不实现垃圾回收。 同样,当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常;5. 运行时常量池
首先,运行时常量池位于方法区内,属于方法区的一部分。存放的是编译期生成的各种字面量和符号引用。在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。。在创建类和接口的运行时常量池时,可能会发生如下异常情况:- 当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。
6. 本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。所以说,当虚拟机使用到其他语言的时候,就会使用到本地方法栈。 而如果Java虚拟机不支持native方法,并且自己也不依赖传统栈的话,可以无需支持本地方法栈。如果支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配。 本地方法栈和虚拟机栈性质类似,可以固定大小,也可以动态扩展,异常抛出也一样。
The Java® Virtual Machine Specification地址(其中包含了JDK6,JDK7,JDK8,JDK9各个版本):
参考自:《深入理解JAVA虚拟机》