1️⃣程序计数器
-
记录正在执行的虚拟机字节码指令的地址
-
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制
-
在多线程的情况下程序计数器用于记录当前线程的执行位置,当线程切换回来时,能够知道上次运行到哪里了
2️⃣虚拟机栈
方法调用的数据需要通过栈进行传递,每一次方法调用就会压入一个栈帧,每一个方法调用结束后就会有一个栈帧弹出
每个栈帧中包含:
-
局部变量表:存放了方法的参数和局部变量
-
操作数栈:存放方法执行过程中产生的中间计算结果,临时变量
-
动态链接:主要服务与一个方法调用其他方法的场景,当一个方法要调用其他方法时,需要将常量池中指向方法的符号引用转化为在内存中的直接引用
-
方法返回地址
栈中可能出现的两种错误:
-
StackOverFlowError:若栈内存大小不允许动态扩展,那么当栈的深度超过最大深度时抛出
-
OutofMemoryError:若栈内存大小允许动态扩展,如果虚拟机在动态扩展时无法申请到足够的内存空间时,则抛出
3️⃣本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法(Native)服务
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
4️⃣堆
存放对象实例,几乎所有的对象实例和数组都是在这里分配内存,是垃圾收集的主要区域
堆不需要连续内存空间,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常
5️⃣方法区
方法区是规范,元空间是实现
用于存放已被虚拟机加载的类的信息,常量,静态变量,即时编译器编译后的代码等数据
它是线程共享的,所有线程可以共同访问方法区中的数据。 它主要用于存储字符串池、方法数据、常量数据等,以加速类的加载和执行,减少运行时的内存消耗。
在HotSpot JVM中,方法区的实现为永久代(Permanent Generation),但从JDK1.8开始,已经不再使用永久代,而是使用元空间(Metaspace)代替。元空间不再受堆内存大小限制,它的大小由操作系统内存限制和-XX:MaxMetaspaceSize参数等因素决定。
运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常
HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。