JVM---运行时数据区-Java虚拟机栈

 

Java虚拟机栈

/**
     * 【运行时数据区-Java虚拟机栈】
     *
     *          JVM指令是 基于栈的指令架构,基于寄存器的指令架构依赖具体的平台,所以基于栈的指令架构可以跨平台;
     *          栈是运行时的单位,堆是存储的单位;
     *
     *          Java虚拟机栈Java Virtual Machine Stack:
     *
     *              what?
     *                  每个 线程 在创建时 都会创建一个 Java虚拟机栈, Java虚拟机栈 内 保存 一个个的栈桢(对应着 一次次的方法调用);
     *                  线程私有;
     *                  声明周期同当前线程;
     *
     *              作用:
     *                  主管Java程序的运行;
     *                  保存 方法的 局部变量、临时计算结果,并 参与 方法的调用 和 返回;
     *
     *              栈的特点:
     *                  快速有效的分配存储方式,访问速度仅次于PC寄存器;
     *                  JVM对 Java虚拟机栈的操作:入栈、出栈;
     *                  Java虚拟机栈 不存在 GC问题;
     *
     *              Java虚拟机栈可能存在的异常:
     *                  ***Java虚拟机 允许 Java虚拟机栈 的大小 是 动态的 或者 是 固定的;
     *
     *                  a,如果采用 固定大小, 每个线程的 Java虚拟机栈 容量 在创建的时候选定;
     *                      如果 线程 请求的栈容量 超过 Java虚拟机栈 允许的最大容量,JVM 将抛出 StackOverflowError;
     *                  b,如果采用 动态扩展,
     *                      Java虚拟机栈 在尝试扩展时 无法 申请到 足够的内存 (或者 线程 创建 没有足够内存 去创建 Java虚拟机栈时)时,JVM将会 抛出 OutofMemoryError;
     *
     *              Java虚拟机栈大小设置:
     *                  -Xss
     *                  默认以byte为单位,后面加单位进行单位变更;
     *                      eg: 1KB, 1MB, 1GB...
     *
     *              Java虚拟机栈的存储结构:
     *                  栈中 的 数据 以 栈桢Stack Frame 的格式存在;
     *                  每个方法 对应 一个 栈桢;
     *                  JVM对Java虚拟机栈的操作只有2个,就是对栈桢的 压栈push、出栈pop,遵循 先进后出的 原则;
     *                  在一条活动线程中,一个时间点上,只有一个活动的栈桢,即只有当前正在执行的方法的栈桢是有效的,被称为 当前栈桢;
     *                  执行引擎运行的所有字节码指令 只对 当前栈桢进行操作;
     *                  如果该方法调用了其他方法,其他方法的新栈桢会被创建出来,放在栈的顶端,称为新的当前栈桢;
     *
     *                  ****不同线程中的栈桢 是 不允许 存在相互引用的,不可能在一个栈桢中引用另外一个线程的栈桢;
     *
     *                 Java方法的2种返回方式(2种方式 都会将 当前栈桢 弹出):
     *                      a,return指令
     *                      b,抛出异常
     *
     */

  

栈桢-局部变量表

/**
     *  【Java虚拟机栈-栈桢-局部变量表】
     *                         1、局部变量表local variables
     *                              局部变量表 本质是一个 一维数字数组;
     *
     *                              作用:
     *                                  存储 方法参数、定义在方法内的局部变量(包括 基本数据类型、引用数据类型、returnAddress类型)的值;
     *
     *                              内存大小:
     *                                  局部变量表所需内存大小 在 编译期 就确定,并 保存在 方法的Code属性的 maximum local variables数据项中(运行期 不会 改变);
     *
     *                              eg :
     *
     *                                   12   public void test(int a){
     *                                   13       Date date = null;
     *                                   14       String s = null;
     *                                   15       int aa = 0;
     *                                   16   }
     *
     *                                      bytecode:
     *                                          0 aconst_null
     *                                          1 astore_2
     *                                          2 aconst_null
     *                                          3 astore_3
     *                                          4 iconst_0
     *                                          5 istore 4
     *                                          7 return
     *
     *                                      LineNumberTable:
     *                                          字节码指令 与 Java源代码 的映射关系;
     *
     *                                         Nr 起始PC 行号
     *                                          0	0	13
     *                                          1	2	14
     *                                          2	4	15
     *                                          3	7	16
     *
     *                                      LocalVariablesTable:
     *                                          方法内的局部变量的描述;
     *
     *                                          Nr 起始PC 长度 序号  名字       描述符
     *                                          0	0	8	0	cp_info #9	cp_info #10
     *                                                              this
     *                                          1	0	8	1	cp_info #13	cp_info #14
     *                                                              a
     *                                          2	2	6	2	cp_info #15	cp_info #16
     *                                                              date
     *                                          3	4	4	3	cp_info #17	cp_info #18
     *                                                              s
     *                                          4	7	1	4	cp_info #19	cp_info #14
     *                                                              aa
     *
     *
     *                              Slot(槽):
     *                                  局部变量表,最基本的存储单元是 Slot;
     *
     *                                  局部变量表中,32位以内的类型 只占 一个Slot,64位的类型 占2个Slot;
     *                                      byte, short, char 在存储前 被 转换为 int,
     *                                      boolean 也被转换为 int,
     *                                      0 表示 false,1 表示 true;
     *
     *                                      ***long、double 占 2个Slot,引用类型、byte、short、int、char、boolean占 1个slot;
     *
     *                                  JVM 为 局部变量表的每个Slot 分配一个访问索引,通过这个索引 可以访问 局部变量表指定的 局部变量值;
     *                                      如果需要访问 局部变量表的64bit的局部变量值时,只需要前一个索引即可;
     *
     *                                  当一个方法 被调用时,它的 方法参数、局部变量 被 按照声明顺序 复制到 局部变量表的每个Slot中;
     *
     *                                  如果当前栈桢 由 构造方法(或 非static方法)创建,该对象引用的this将会 放在 index为0的Slot处,其余 按声明顺序排列;
     *                                      ***为什么static方法中不能使用非static?
     *                                              static方法中的局部变量表中没有this;
     *
     *                                  局部变量表的Slot 是可以重复利用的;
     *
     *                              局部变量表的 变量 也是 GC的 根节点;
     *                                  只要被 局部变量 直接(或 间接)引用的 对象 不会被回收;
     */

  

栈桢-操作数栈

用途

  用于方法的字节码指令执行时 存放操作数和临时结果;

/**
     *  【Java虚拟机栈-栈桢-操作数栈】
     *                         2、操作数栈operand stack
     *
     *                              本质是一个 一维数字数组;
     *
     *                              eg :
     *                                      public void test(int a){
     *
     *                                          byte b = 15;
     *                                          int c = 8;
     *
     *                                          int d = b + c;
     *                                      }
     *
     *
     *                                      PC寄存器:
     *                                      0-2-3-5-6-7-8-9-11
     *
     *                                      0 bipush 15         将 15 push到 操作数栈
     *                                      2 istore_2          将 操作数栈 的15 pop,存储到 局部变量表的 2序号slot
     *                                      3 bipush 8          将 8 push到 操作数栈
     *                                      5 istore_3          将 操作数栈 的8 pop,存储到 局部变量表的 3序号slot
     *                                      6 iload_2           从 局部变量表的2序号slot 取出值15,push到 操作数栈
     *                                      7 iload_3           从 局部变量表的3序号slot 取出值8,push到 操作数栈
     *                                      8 iadd              将 操作数栈的8pop,将 操作数栈的15pop,然后 由执行引擎 将字节码指令翻译为本地机器指令, 然后 由CPU 进行相加运算得结果23,将23push到操作数栈;
     *                                      9 istore 4          将 操作数栈中的23pop,将23 存储到 局部变量表的4序号slot
     *                                      11 return
     *
     *                                      LocalVariableTable
     *                                                  序号
     *                                      0	0	12	0	cp_info #9	cp_info #10
     *                                                          this
     *                                      1	0	12	1	cp_info #13	cp_info #14
     *                                                          a
     *                                      2	3	9	2	cp_info #15	cp_info #16
     *                                                          b
     *                                      3	6	6	3	cp_info #17	cp_info #14
     *                                                          c
     *                                      4	11	1	4	cp_info #18	cp_info #14
     *                                                          d
     *
     *                              作用:
     *                                  在 方法执行过程中,根据字节码指令,往 操作数栈 中 写入push (提取pop) 数据;
     *
     *                              内存大小:
     *                                  在 编译期 确定,保存在 方法的Code属性的 max_stack ;
     *
     *                              根据 字节码指令 将 操作数 写入 操作数栈 (或 将 操作数 从 操作数栈 取出),CPU计算完成后,将 结果 再写入 操作数栈;
     *                              如果 方法有返回值,其返回值 将被 写入 当前栈桢 的 操作数栈中,并 更新 PC寄存器中 下一条要执行的字节码指令;
     *                              JVM的 解释引擎 是 基于 栈 的执行引擎,其中 栈 指的就是 操作数栈;
     *
     *                              ***byte, short, char, boolean 在 进行 运算时 ,都会转化为 int 类型 进行计算;
     *
     *                              栈顶缓存技术:
     *                                  HotSpot JVM设计者,为了 解决 频繁 执行 内存 读/写,将 栈顶元素 全部 缓存 在物理CPU的寄存器中;
     */

  

栈桢-动态链接

what

  程序执行过程中, 将 方法调用的符号引用 转换为 直接引用的过程

  通过 方法区运行时常量池 来实现;

 

符号引用&直接引用  

  符号引用:

    是在编译时生成的类文件中的信息,它包含了方法和字段的名字、描述符以及它们所在的类或接口的全限定名。

    这些信息被存储在类文件的常量池中

  直接引用:

    是程序运行期间可以直接定位到目标方法或字段的具体地址

    这个地址可能是一个指针、一个偏移量或者是一个句柄等具体的数据结构。

 

方法调用过程

当一个方法被调用时,JVM会首先查找该方法的符号引用,并将其转化为直接引用。这个过程通常涉及到以下步骤:

  1. 解析:JVM检查当前栈帧中的方法引用是否已经被解析过。如果没有,JVM则会开始解析过程。

  2. 绑定:解析过程实际上就是进行绑定,即将符号引用与具体的内存地址关联起来。这一步骤可以在类加载时完成(静态绑定),也可以在运行时完成(动态绑定)。对于虚方法(非final和非private的方法),动态绑定会在每次方法调用时进行。

  3. 访问权限检查:在将符号引用转化为直接引用的过程中,JVM还会进行访问权限检查,确保当前线程具有调用该方法的权限。

  4. 方法调用:一旦方法的直接引用确定,JVM就可以通过这个引用来调用方法了;

  

/**
     *  【Java虚拟机栈-栈桢-动态链接】
     *                         动态链接dynamic linking
     *                              what?
     *                                  当前Class 常量池 在 方法区常量池 的引用;
     *                              作用:
     *                                  将 运行时常量池 的符号引用 转换为 直接引用;
     *
     *                              Java源代码 经过 编译到 字节码文件中时,所有的变量、方法引用 都作为 符号引用 保存在 class文件的常量池中;
     *
     *                              每个 栈桢 内部 都包含 一个 指向 运行时常量池 该栈桢所属方法 的引用;
     *                                  引用 的目的:
     *                                      支持当前方法的代码 能够 实现 动态链接,比如 invokedynamic指令;
     *
     *                              ***方法调用***
     *                                  在 JVM中,将 符号引用 转化为 直接引用 与 方法的绑定机制 相关;
     *
     *                                      绑定:
     *                                          一个字段(或 方法, 或 类)在 符号引用 被 转化为 直接引用 的过程,仅发生一次;
     *
     *                                      方法的绑定机制:
     *                                          静态链接(早期绑定):
     *                                              当一个 Class文件 被 加载到 JVM内存时,如果 被调用的 目标方法 编译期 可知 , 且 运行期 不变;
     *                                              这种 情况下 将 调用方法的 符号引用 转化为 直接引用 的过程 称为 静态链接;
     *                                              eg: 非虚方法
     *
     *                                          动态链接(晚期绑定):
     *                                              当一个 Class文件 被 加载到 JVM内存时,如果 被调用的 目标方法 编译期 无法确定,只能在程序 运行期 将 符号引用 转化为 直接引用;
     *                                              这种 引用 转化过程 具备 动态性,称为 动态链接;
     *                                              eg:虚方法
     *
     *                                      ***多态的实现***
     *                                          多态的使用前提:
     *                                              1、继承关系 2、方法重写
     *
     *
     *                              ***虚方法与非虚方法***
     *                                      Java中的任何一个 普通方法 都具备虚方法的特征,如果不希望某个方法拥有虚方法的特征时,可以使用final修饰;
     *
     *                                      非虚方法:
     *                                          如果 方法 在编译期 就确定了 具体的版本 , 且 运行期 不可变,这样的方法就是 非虚方法;
     *                                              eg:static方法、private方法、final方法、构造器、子类调用指定的父类方法;
     *
     *                                                  invokestatic:调用 static方法;
     *                                                  invokespecial:调用 构造器、private方法、父类方法;
     *                                                  invokevirtual:调用 final方法;
     *
     *                                      虚方法:
     *                                          方法 在编译期 无法确定 具体的版本;
     *
     *                                              invokevirtual:调用 虚方法;
     *                                              invokeinterface:调用 接口方法;
     *
     *                                      动态调用指令:
     *                                          invokedynamic
     *                                              Java7 增加了一个 invokedynamic指令,使得Java增加了动态语言类型支持;
     *
     *                                              动态解析出需要调用的方法,然后执行;
     *                                              invokestatic、invokespecial、invokevirtual、invokeinterface 方法的调用执行不可人为干预,而invokedynamic则支持用户确定方法版本;
     *
     *                                          ***动态类型语言与静态类型语言***
     *                                              对类型的检查 在编译期,就是静态类型语言;
     *                                                  判断变量自身的类型信息;
     *
     *                                              对类型的检查 在运行期,就是动态类型语言;
     *                                                  判断变量值的类型信息;
     *
     *                                              区别:
     *                                                  对 类型的检查 是在 编译期 还是 运行期;
     *
     *                                              eg:
     *                                                  interface Fun{
     *                                                      boolean exec(String s);
     *                                                  }
     *
     *                                                  public class DLTest {
     *
     *                                                      public void exec(Fun fun){}
     *
     *                                                      public static void main(String[] args) {
     *
     *                                                          DLTest dlTest = new DLTest();
     *
     *                                                          dlTest.exec(s -> false);
     *
     *                                                      }
     *
     *                                                      0 new #2 <com/an/stack/dynamiclinking/DLTest>
     *                                                      3 dup
     *                                                      4 invokespecial #3 <com/an/stack/dynamiclinking/DLTest.<init>>
     *                                                      7 astore_1
     *                                                      8 aload_1
     *                                                      9 invokedynamic #4 <exec, BootstrapMethods #0>
     *                                                      14 invokevirtual #5 <com/an/stack/dynamiclinking/DLTest.exec>
     *                                                      17 return
     *
     *                                                  }
     *
     *
     *                             ***方法重写***
     *                                      方法重写的本质:
     *                                          1、找到 操作数栈 的元素的对象的实际类型C;
     *                                          2、如果 在类型C中找到与常量池中描述符相符的方法,则进行访问权限校验,如果通过,则返回这个方法的直接引用,查找过程结束;
     *                                                                                                    如果不通过,则返回 java.lang.IllegalAccessError;
     *
     *                                             如果 在类型C中找不到,则根据继承关系,依次对C的各个父类进行搜索,如果父类中找不到,抛出java.lang.AbstractMethodError;
     *
     *                             ***虚方法表***
     *                                      在面向对象编程中,很频繁使用 动态链接,如果每次动态链接都要重新查找,严重影响效率;
     *                                          因此,为了提升性能,JVM在类的方法区建立一个虚方法表virtual method table(非虚方法不出现在表中)来实现;
     *
     *                                      每个类都有一个虚方法表,表中放着各个虚方法的实际入口;
     *
     *
     *
     */

  

 

栈桢-方法返回地址

/**
     *  【Java虚拟机栈-栈桢-方法返回地址】
     *                          方法返回地址return address
     *                              作用:
     *                                  存放 调用当前方法 PC寄存器的值;
     *
     *                                  eg:
     *                                      方法A 调用 方法B
     *                                          当前栈桢B 正常执行完成后,将 方法A调用方法B的指令地址 交由 执行引擎;
     *
     *                              方法的结束:
     *                                  a,正常执行完成
     *                                  b,出现未处理异常,非正常退出
     *
     *                              无论 方法通过 哪种方式退出, 在方法退出后 都返回 该方法被调用的位置;
     *                                  a,正常退出时,调用当前方法的 PC寄存器的值 作为返回地址;
     *                                  b,异常退出时,返回地址 通过 异常表 来确定,栈桢不会保存 这部分信息;
     *
     *                              ***正常退出 与 异常退出 的区别***
     *                                  通过 异常退出的方法 不会 给 调用者 返回任何值;
     *
     *
     *                                  正常退出:
     *                                      执行引擎 遇到任意一个方法返回的字节码指令,会有返回值传递给上层方法调用者,称为 正常完成出口;
     *
     *                                      一个方法 在 正常调用完成后 需要使用哪个返回指令 需要根据 方法返回值 的实际数据类型 确定;
     *                                        ireturn:
     *                                            返回值类型是 byte, short, int, char, boolean
     *                                        lreturn:
     *                                            返回值类型是 long
     *                                        freturn:
     *                                            返回值类型是 float
     *                                        dreturn:
     *                                            返回值类型是 double
     *                                        areturn:
     *                                            返回值类型是 引用类型
     *                                        return:
     *                                            void方法、构造器
     *
     *                                  异常退出:
     *                                      在方法执行的过程中遇到了一次,且这个异常没有在方法内处理(在本方法的异常表中没有找到匹配的异常处理器),就会导致方法退出,称为 异常完成出口;
     *
     *                                      方法执行过程中抛出异常时的异常处理,存储在一个异常表;
     *
     *                                      eg:
     *                                          public void test1() {
     *                                              try {
     *                                                  test2();
     *                                              } catch (ClassNotFoundException e) {
     *                                                  e.printStackTrace();
     *                                              }
     *                                          }
     *
     *                                                       0: aload_0
     *                                                       1: invokevirtual #2                  // Method test2:()Ljava/lang/String;
     *                                                       4: pop
     *                                                       5: goto          13
     *                                                       8: astore_1
     *                                                       9: aload_1
     *                                                       10: invokevirtual #4                  // Method java/lang/ClassNotFoundException.printStackTrace:()V
     *                                                       13: return
     *
     *                                               Exception table:
     *                                                  from    to  target type
     *                                                  0     4     7   Class java/lang/ClassNotFoundException
     *
     *
     *
     */

  

 

posted on 2022-04-02 11:00  anpeiyong  阅读(16)  评论(0编辑  收藏  举报

导航