从整体上看JVM的内存分为两大类:线程私有的和线程共享的。
线程私有:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享:
- 堆区
- 方法区
程序计数器
主要作用就是记住下一条JVM指令的执行地址。因为在多线程的情况下,同一个时间单核CPU只会执行一个线程中的方法,也就是说CPU会不断切换执行的线程,那么线程会不断的中断和恢复,这就要求线程在中断前就要记住当前的运行状态,从而下一次恢复时能继续向下执行。
虚拟机栈
其实就是我们通常理解的栈,而栈内又存放这一个一个栈帧,每一次的方法调用都会有一个新的栈帧入栈。
每一个栈帧主要包含四个方面:
- 局部变量表
- 主要用于存储方法参数和方法内的局部变量,局部变量又包含:Java基本的数据类型、对象的引用
- 局部变量表所需要的大小是在编译期内确定的
- 由于局部变量只在当前变量中有效,所以不存在线程安全的问题
- 局部变量表最基本的存储单元是Slot(为什么很多的地方都有最基本的存储单元,他的好处是什么?),32位以内的类型只占用一个Slot,64位的类型(long和double)占用两个连续的Slot,byte、short、char在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true。
为什么byte、short、char在存储前被转换为int?
有一种说法是:因为JVM指令中的操作码只占一个字节,也就是说最多只有256个不同的操作码,如果给每一个数据类型都定义一个不同的操作码的话会不够用。比如:iadd,将栈中两个int类型相加,但是并没有针对short类型的“sadd”.【2】 - 阿萨德
- 操作数栈
- 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
-
int a = 5; int b = 10; int result = a + b; // 对应字节码 0: iconst_5 // Push the value 5 onto the stack 1: istore_1 // Store the value into local variable 'a' 2: bipush 10 // Push the value 10 onto the stack 4: istore_2 // Store the value into local variable 'b' 5: iload_1 // Push the value of 'a' onto the stack 6: iload_2 // Push the value of 'b' onto the stack 7: iadd // Pop the top two values, add them, and push the result 8: istore_3 // Store the result into local variable 'result'
- 动态链接
- 动态链接是指向运行时常量池的方法引用
- 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在Class文件的常量池(Class文件中都包含什么?)中。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
- 静态链接 or 动态链接,早期绑定 or 晚期绑定
- 虚方法和非虚方法:如果方法在编译器就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法,比如静态方法、私有方法、final 方法、实例构造器、父类方法都是非虚方法,其他方法称为虚方法。
- 虚方法表:在面向对象编程中,会频繁的使用到动态分派,如果每次动态分派都要重新在类的方法元数据中搜索合适的目标有可能会影响到执行效率。为了提高性能,JVM 采用在类的方法区建立一个虚方法表,使用索引表来代替查找。非虚方法不会出现在表中。每个类中都有一个虚方法表,表中存放着各个方法的实际入口。虚方法表会在类加载的连接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM 会把该类的方法表也初始化完毕。
- 方法返回地址
- 用来存放调用该方法的PC寄存器的值。方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定的,栈帧中一般不会保存这部分信息。