java虚拟机栈是线程私有的,他与线程的声明周期同步。虚拟机栈描述的是java方法执行的内存模型,每个方法执行都会创建一个 栈帧 ,栈帧包含局部变量表、操作数栈、动态连接、方法出口等。
每一个方法的执行到执行完成,对应着一个栈帧在虚拟机中从入栈到出栈的过程。java虚拟机栈栈顶的栈帧就是当前执行方法的栈帧。PC寄存器会指向该地址。当这个方法调用其他方法的时候久会创建一个新的栈帧,这个新的栈帧会被方法Java虚拟机栈的栈顶,变为当前的活动栈,在当前只有当前活动栈的本地变量才能被使用,当这个栈帧所有指令都完成的时候,这个栈帧被移除,之前的栈帧变为活动栈,前面移除栈帧的返回值变为这个栈帧的一个操作数。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。在编译代码的时候,栈 帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。 一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧的概念结构如下图所示:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中方法的符号引用为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态方法,私有方法等),这种转化称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。由于篇幅有限这里不再继续讨论解析与分派的过程,这里只需要知道静态解析与动态连接的区别就好。
当一个方法开始执行后,只有 两种方式可以退出这个方法 :
实例讲解: java代码:
public class Test { private static int add(int c){ return c + 10; } public static void main(String[] args) { int a, b, c; a = 1; b = 2; c = add(a*b); c = c*(a+b); } } 复制代码
使用javap -v 反编译后的
Classfile /E:/Code/JAVA/JUC/out/production/JUC/net/ziruo/juc/Test.class Last modified 2019-10-29; size 546 bytes MD5 checksum 3526f85e07771be800502f7e10b50a3a Compiled from "Test.java" public class net.ziruo.juc.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#24 // java/lang/Object."<init>":()V #2 = Methodref #3.#25 // net/ziruo/juc/Test.add:(I)I #3 = Class #26 // net/ziruo/juc/Test #4 = Class #27 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 Lnet/ziruo/juc/Test; #12 = Utf8 add #13 = Utf8 (I)I #14 = Utf8 c #15 = Utf8 I #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 a #21 = Utf8 b #22 = Utf8 SourceFile #23 = Utf8 Test.java #24 = NameAndType #5:#6 // "<init>":()V #25 = NameAndType #12:#13 // add:(I)I #26 = Utf8 net/ziruo/juc/Test #27 = Utf8 java/lang/Object { public net.ziruo.juc.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lnet/ziruo/juc/Test; private static int add(int); descriptor: (I)I flags: ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: bipush 10 3: iadd 4: ireturn LineNumberTable: line 12: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 c I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: imul 7: invokestatic #2 // Method add:(I)I 10: istore_3 11: iload_3 12: iload_1 13: iload_2 14: iadd 15: imul 16: istore_3 17: return LineNumberTable: line 18: 0 line 19: 2 line 20: 4 line 21: 11 line 22: 17 LocalVariableTable: Start Length Slot Name Signature 0 18 0 args [Ljava/lang/String; 2 16 1 a I 4 14 2 b I 11 7 3 c I } 复制代码
主要代码注释讲解:
0: iconst_1 (把1压入操作数栈栈顶) 1: istore_1 (把操作数栈栈顶的出栈放入局部变量表索引为1的位置) 2: iconst_2 (把2压入操作数栈栈顶) 3: istore_2 (把操作数栈栈顶的出栈放入局部变量表索引为2的位置) 4: iload_1 (把局部变量表索引为1的值放入操作数栈栈顶) 5: iload_2 (把局部变量表索引为2的值放入操作数栈栈顶) 6: imul (把操作数栈栈顶的和栈顶下面的一个进行乘法运算后放入栈顶) 7: invokestatic #2 // Method add:(I)I (执行静态方法,返回的值放入操作数栈) 10: istore_3 (把操作数栈栈顶的出栈放入局部变量表索引为3的位置) 11: iload_3 把局部变量表索引为3的值放入操作数栈栈顶) 12: iload_1 把局部变量表索引为1的值放入操作数栈栈顶) 13: iload_2 把局部变量表索引为2的值放入操作数栈栈顶) 14: iadd (把操作数栈栈顶的和栈顶下面的一个进行加法运算后放入栈顶) 15: imul (把操作数栈栈顶的和栈顶下面的一个进行乘法运算后放入栈顶) 16: istore_3 (把操作数栈栈顶的出栈放入局部变量表索引为3的位置) 17: return (结束) 复制代码
参考文章: