JVM运行时数据区

时间:2022-07-22
本文章向大家介绍JVM运行时数据区,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Java程序运行时JVM会把内存分为如下图所示的几个区域,其中包括线程所共享的堆、方法区以及线程所独有的虚拟机栈、本地方法栈和程序计数器。

一、程序计数器

程序计数器(Program Counter Register),可以看做当前线程所执行字节码行号的指示器。为了线程切换后能够恢复到之前切换前的位置,每条线程的程序计数器应该是互不干扰的。

此外若当前线程执行的是一个Java方法,此时其程序计数器中记录的是正在执行字节码指令的地址。若其是一个本地方法(c,c++方法),此时计数器值为空。

二、虚拟机栈

虚拟机栈(VMStack),该块区域也是线程私有的。虚拟机栈描述的是Java方法执行的线程模型:每个方法执行前,JVM会为该方法创建一个栈帧(Stack Frame)放入栈中,执行完毕后该栈帧出栈。

可以想象到的是当前方法(记做funA)调用另一方法(记做funB)时会为新方法产生新的栈帧存入栈顶,当funB执行完毕后将funB的栈帧出栈,然后接着执行funA方法体中剩下的代码,当funA执行完毕后,funA对应的栈帧才出栈。

对于执行引擎而言,在活动线程中只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是生效的。该栈帧被称为当前栈帧,该方法称为当前方法。

栈帧结构如下图所示,其中包括局部变量表,操作栈等.

局部变量表

局部变量表(local variables table),其的最大空间在编译期已经确定了,方法的Code属性的max_locals指的就是局部变量表的最大空间,用于存放方法参数和方法内部定义的局部变量。如下代码中的v1、v2就是存储于局部变量表中。我们常说的”栈”内存一般指的就是局部变量表。

public class Main{
    public void fun(int v1){
        int v2;
    }
}

局部变量表以变量槽(variable slot)为最小单位,每个变量槽都能存放一个boolean、byte、char、short、int、float、reference类型的数据,对于long类型和double类型采用分割存储的方式。

当一个方法被调用时,JVM会使用局部变量表完成实参到形参的传递;如果执行的是实力方法(非静态方法),局部变量表弟0位索引的变量槽默认传递的是对象实例的引用。

操作数栈

操作数栈(Operand Stack)又称为操作栈,其最大深度在编译期也已经确定了,max_stacks指的就是其的最大深度。32位数据类型所占栈容量为1,64位数据类型所占的栈容量为2。

当一个方法开始执行时,该方法的操作数栈为空,方法执行过程中会有各种字节码指令对操作数栈进行出栈、入栈处理。

    public static void main(String[] g) {
        int x = 100;
        int y = 101;
        int z = x + y;
    }
    Code:
       0: bipush        100 // 将常量100加载到操作数栈
       2: istore_1          // 将操作数栈顶元素存入局部变量表1号槽
       3: bipush        101 
       5: istore_2                    
       6: iload_1            // 将局部变量表1号槽内容加载到操作数栈
       7: iload_2            // 将局部变量表2号槽内容加载到操作数栈
       8: iadd                // 将栈顶两数弹出并相加,并将结果重新入栈
       9: istore_3

三、本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈发挥作用类似,虚拟机栈对应的是Java方法,本地方法栈对应的是本地方法。

四、堆

Java堆(Heap)是虚拟机管理的内存最大的一块,“几乎”所有对象实例都是在堆上分配的。其也是所有线程共享的一块区域,在虚拟机启动时创建。

五、方法区

方法区(Method Area)也是各个线程共享的内存区域,用于存储已经被虚拟机加载了的类信息,常量,静态变量等。《Java虚拟机规范》中对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或可扩展外,甚至可以选择不实现垃圾回收。但是对该区域进行垃圾回收时必要的,主要针对常量池的回收和类的卸载。