精华内容
下载资源
问答
  • 深入理解Java虚拟机栈的栈帧

    万次阅读 多人点赞 2019-08-25 00:05:48
    本节将会介绍一下Java虚拟机栈中的栈帧,会对栈帧的组成部分(局部变量表、操作数栈、动态链接、方法出口)分别进行介绍,最后还会通过javap命令反解析编译后的.class文件,进行分析方法执行时的局部变量表、操作数...

    本节将会介绍一下Java虚拟机栈中的栈帧,会对栈帧的组成部分(局部变量表、操作数栈、动态链接、方法出口)分别进行介绍,最后还会通过javap命令反解析编译后的.class文件,进行分析方法执行时的局部变量表、操作数栈等。

    目录

    Java虚拟机栈概述

    局部变量表

    操作数栈

    动态连接

    方法的返回地址

    结合javap命令理解栈帧


    Java虚拟机栈概述

    Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:栈帧(Stack Frame)是用于支持Java虚拟机进行方法调用和执行的数据结构,它是虚拟机栈中的栈元素。每个方法在执行的同到都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

    在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

    每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟栈中从入栈到出栈的过程(说人话就是要执行一个方法,将该方法的栈帧压入栈顶,方法执行完成其栈帧出栈)。在JVM里面,栈帧的操作只有两种:出栈和入栈。正在被线程执行的方法称为当前线程方法,而该方法的栈帧就称为当前帧,执行引擎运行时只对当前栈帧有效。

    下面对栈帧的每个组成部分分别介绍一下。

    局部变量表

    局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时就在方法的code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。

    局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小,只是很有导向性地说到每个引都应该能存放一个boolean、byte、char、short,int,float、reference或returnAddress类型的数据,这8种数类都可以使用32位或更小的物理存来存放,但这种描述与明确指出 "每个Slot占用32位长度的内存空间" 是有一些差别的,它运行Slot的长度可以随着处理器、操作系统或虚拟机的不同而发生变化。只要保证使在64位虚拟机中使用了64位的物理内存空间去实现一个Slot,虚拟机仍要使用对齐和补白的手段让Slot在外观上看起来与32位拟机中的一致。

    一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、float、reference和returnAddress8种类型。第7种reference类表示对一个对象实例的引用,虚拟机规范既没有说明它的长度,也没有明确指出这种引用应有怎样的结构。但是一般来说,虚拟机实现至少都应当能通过这个引用做到两点,一是从此引用直接或间接地查找到对象在Java堆中的数据存放的起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息。第8种即returnAddress类型目前已经很少见了,现在已经由异常表代替。

    对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的引Slot空间。Java语言中明确的(reference类型则可能是32位也可能是64位),64位的数据类型只有long和double两种。虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。如果访问的是32位数据类型的变量,索引 n 就代表了使用第n个Slot,如果是64位数据类型的变量,则说明会同时使用n和n+1两个Slot对于两个相邻的共同存放一个64位数据的两个Slot,不允许采用任何方式单独访问其中的某一个,Java虚拟机规范中明确要求了如果遇到进行这种操作的字节码序列,虚拟机应该在类加载的校验阶段抛出异常。

    如果是实例方法(非static的方法),那么局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用"this"。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot(比如方法method(int a1,inta2),参数表为a1和a2,则局部变量表索引0、1、2则分别存储了this指针、a1、a2,如果方法内部有其他内部变量,则在局部变量表中存在a2之后的位置)。

    为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。

    局部变量不像的类成员变量那样存在"准备阶段"。我们知道类变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的初始值。因此,即使在初始化阶段程序员没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值。但局部变量就不一样,如果一个局部变量定义了但没有赋初始值是不能使用的,不要认为Java中任何情况下都存在诸如整型变量默认为0,布尔型变量默认为false等这样的默认值。

    操作数栈

    操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在maxstacks数据项中设定的最大值。

    当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。 

    Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。如果当前线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

    动态连接

    每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。

    Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

    Math math=new Math();
    math.compute();//调用实例方法compute()

    以上面两行代码为例,解释一下动态连接:math.compute()调用时compute()叫符号,需要通过compute()这个符号去到常量池中去找到对应方法的符号引用,运行时将通过符号引用找到方法的字节码指令的内存地址。

    方法的返回地址

    当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。

    无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整pc计数器的值以指向方法调用指令后面的一条指令等。

    结合javap命令理解栈帧

    上面进行了大段的文文字介绍,还是不太好理解,下面我们通过javap命令来分析一下方法中的操作指令、局部变量表、操作数栈等。

    javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。下面是其用法说明:

    D:\wyonline\myworkspaces\framework\Test\bin\com\wkp\jvm>javap
    用法: javap <options> <classes>
    其中, 可能的选项包括:
      -help  --help  -?        输出此用法消息
      -version                 版本信息
      -v  -verbose             输出附加信息
      -l                       输出行号和本地变量表
      -public                  仅显示公共类和成员
      -protected               显示受保护的/公共类和成员
      -package                 显示程序包/受保护的/公共类
                               和成员 (默认)
      -p  -private             显示所有类和成员
      -c                       对代码进行反汇编
      -s                       输出内部类型签名
      -sysinfo                 显示正在处理的类的
                               系统信息 (路径, 大小, 日期, MD5 散列)
      -constants               显示最终常量
      -classpath <path>        指定查找用户类文件的位置
      -cp <path>               指定查找用户类文件的位置
      -bootclasspath <path>    覆盖引导类文件的位置

    下面我们写一个简单的Java程序:

    package com.wkp.jvm;
    
    public class Math {
    
    	public static final Integer CONSTANT=666;
    	
    	public int compute() {//一个方法对应一块栈帧内存区域
    		int a=3;
    		int b=5;
    		int c=(a+b)*10;
    		return c;
    	}
    	
    	public static void main(String[] args) {
    		Math math=new Math();
    		math.compute();
    	}
    }

    然后进入到Math.class所在目录执行: javap -c Math.class > Math.txt 命令,将Math.class字节码文件反汇编然后输出到Math.txt文件中:

    然后我们查看Math.txt的内容如下:我们重点分析下compute方法内的指令,其内部的指令后面我加了注释(这里我是参考上一节的《JVM字节码指令集大全及其介绍》,感兴趣的话可以看一看),注释中的栈就是指的栈帧中的操作数栈,本地变量表就是指的局部变量表。

    Compiled from "Math.java"
    public class com.wkp.jvm.Math {
      public static final java.lang.Integer CONSTANT;
    
      static {};
        Code:
           0: sipush        666
           3: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           6: putstatic     #16                 // Field CONSTANT:Ljava/lang/Integer;
           9: return
    
      public com.wkp.jvm.Math();
        Code:
           0: aload_0
           1: invokespecial #21                 // Method java/lang/Object."<init>":()V
           4: return
    
      public int compute();
        Code:
           0: iconst_3			//将int类型的3推送至栈顶
           1: istore_1			//将栈顶int类型数值(上面的3)出栈并存入第二个本地变量
           2: iconst_5			//将int类型的5推送至栈顶
           3: istore_2			//将栈顶int类型数值(上面的5)出栈并存入第三个本地变量
           4: iload_1			//将第二个int型本地变量(上面的3)推送至栈顶
           5: iload_2			//将第三个int型本地变量(上面的5)推送至栈顶
           6: iadd				//将栈顶两int型数值出栈,然后相加并将结果压入栈顶
           7: bipush        10	        //将常量值10推送至栈顶
           9: imul				//将栈顶两int型数值出栈,然后相乘并将结果压入栈顶
          10: istore_3			//将栈顶int类型数值(上面的乘积)出栈并存入第四个本地变量
          11: iload_3			//将第四个int类型本地变量推送至栈顶
          12: ireturn			//从当前方法返回int类型值
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #1                  // class com/wkp/jvm/Math
           3: dup
           4: invokespecial #33                 // Method "<init>":()V
           7: astore_1
           8: aload_1
           9: invokevirtual #34                 // Method compute:()I
          12: pop
          13: return
    }

    下面我们通过图示简单表示一下上面compute方法中指令操作时关于本地变量表、操作数栈的情况:

    我们先看下第一行  0: iconst_3  //将int类型的3推送至栈顶,可以看到下图3已经被入栈到操作数栈的栈顶。

    我们再看下第二行  1: istore_1 //将栈顶int类型数值(上面的3)出栈并存入第二个本地变量,将上图中栈顶的3出栈然后存入本地表中第二个位置,如下图所示:

    第三行、第四行跟上面的一二行指令类似,第四行指令执行后变成如下所示:

    第五行、第六行中 4: iload_1  //将第二个int型本地变量(上面的3)推送至栈顶; 5: iload_2  //将第三个int型本地变量(上面的5)推送至栈顶,即将局部变量表中的3和5依次压入栈顶,如下图所示:

    然后第七行执行iadd操作,将栈顶的两个int类型数据5和3出栈相加,将得到的和压入栈顶,得到如下结果:

    后面的指令操作过程与上面类似,执行完第12行的iload_3指令之后,会得到如下图所示:

    关于局部变量表的信息,还可以通过javap -l 命令查看如下图所示,另外还可以通过Idea中的jclasslib 查看。

    LocalVariableTable表示的就是局部变量表的信息:

    public int compute();
        LineNumberTable:
          line 8: 0
          line 9: 2
          line 10: 4
          line 11: 11
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
              0      13     0  this   Lcom/wkp/jvm/Math;
              2      11     1     a   I
              4       9     2     b   I
             11       2     3     c   I

    我们还可以通过  javap -c Math.class > Math.txt 查看更多的信息如下:我们可以看到 9: invokevirtual #34  // Method compute:() 中的 #34 可以在常量池中找到 #34 = Methodref    #1.#35  // com/wkp/jvm/Math.compute:()也就是方法的符号引用,运行时通过符号引用解析出来方法的执行指令的内存地址,这个其实就是动态连接。

    Classfile /D:/wyonline/myworkspaces/framework/Test/bin/com/wkp/jvm/Math.class
      Last modified 2019-8-24; size 761 bytes
      MD5 checksum be0cdf4bcd037929d3fe0af86d44a837
      Compiled from "Math.java"
    public class com.wkp.jvm.Math
      minor version: 0
      major version: 52		//魔数
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:		//常量池
       #1 = Class              #2             // com/wkp/jvm/Math
       #2 = Utf8               com/wkp/jvm/Math
       #3 = Class              #4             // java/lang/Object
       #4 = Utf8               java/lang/Object
       #5 = Utf8               CONSTANT
       #6 = Utf8               Ljava/lang/Integer;
       #7 = Utf8               <clinit>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Methodref          #11.#13        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      #11 = Class              #12            // java/lang/Integer
      #12 = Utf8               java/lang/Integer
      #13 = NameAndType        #14:#15        // valueOf:(I)Ljava/lang/Integer;
      #14 = Utf8               valueOf
      #15 = Utf8               (I)Ljava/lang/Integer;
      #16 = Fieldref           #1.#17         // com/wkp/jvm/Math.CONSTANT:Ljava/lang/Integer;
      #17 = NameAndType        #5:#6          // CONSTANT:Ljava/lang/Integer;
      #18 = Utf8               LineNumberTable
      #19 = Utf8               LocalVariableTable
      #20 = Utf8               <init>
      #21 = Methodref          #3.#22         // java/lang/Object."<init>":()V
      #22 = NameAndType        #20:#8         // "<init>":()V
      #23 = Utf8               this
      #24 = Utf8               Lcom/wkp/jvm/Math;
      #25 = Utf8               compute
      #26 = Utf8               ()I
      #27 = Utf8               a
      #28 = Utf8               I
      #29 = Utf8               b
      #30 = Utf8               c
      #31 = Utf8               main
      #32 = Utf8               ([Ljava/lang/String;)V
      #33 = Methodref          #1.#22         // com/wkp/jvm/Math."<init>":()V
      #34 = Methodref          #1.#35         // com/wkp/jvm/Math.compute:()I
      #35 = NameAndType        #25:#26        // compute:()I
      #36 = Utf8               args
      #37 = Utf8               [Ljava/lang/String;
      #38 = Utf8               math
      #39 = Utf8               SourceFile
      #40 = Utf8               Math.java
    {
      public static final java.lang.Integer CONSTANT;
        descriptor: Ljava/lang/Integer;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: sipush        666
             3: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             6: putstatic     #16                 // Field CONSTANT:Ljava/lang/Integer;
             9: return
          LineNumberTable:
            line 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
    
      public com.wkp.jvm.Math();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #21                 // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/wkp/jvm/Math;
    
      public int compute();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=4, args_size=1
             0: iconst_3
             1: istore_1
             2: iconst_5
             3: istore_2
             4: iload_1
             5: iload_2
             6: iadd
             7: bipush        10
             9: imul
            10: istore_3
            11: iload_3
            12: ireturn
          LineNumberTable:
            line 8: 0
            line 9: 2
            line 10: 4
            line 11: 11
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      13     0  this   Lcom/wkp/jvm/Math;
                2      11     1     a   I
                4       9     2     b   I
               11       2     3     c   I
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #1                  // class com/wkp/jvm/Math
             3: dup
             4: invokespecial #33                 // Method "<init>":()V
             7: astore_1
             8: aload_1
             9: invokevirtual #34                 // Method compute:()I
            12: pop
            13: return
          LineNumberTable:
            line 15: 0
            line 16: 8
            line 17: 13
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      14     0  args   [Ljava/lang/String;
                8       6     1  math   Lcom/wkp/jvm/Math;
    }
    SourceFile: "Math.java"

    参考:《深入理解Java虚拟机第二版》、《Java虚拟机规范 JavaSE8版》

    展开全文
  • 1.总述栈帧 java虚拟机以方法作为基本的执行单元。这个执行单元的数据结构就是 虚拟机栈 中的 栈元素——栈帧。...在编译java源码时,栈帧中需要多大的局部变链表,操作数栈的深度这些已经被分析出来并

    1.总述栈帧

    java虚拟机以方法作为基本的执行单元。这个执行单元的数据结构就是 虚拟机栈 中的 栈元素——栈帧

    栈帧的结构如下:

    如上图所示,栈帧存储了方法的局部变量表,操作数栈,动态连接、方法返回地址和一些额外的附加信息。

    对于虚拟机的执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是生效的,即只有当前栈帧是生效的,与当前栈帧关联的方法叫当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

    在编译java源码时,栈帧中需要多大的局部变链表,操作数栈的深度这些已经被分析出来并写到了方法表的Code属性中了,如下图是一个main方法的栈帧中的局部变量表大小等数据:

    2.局部变量表

    局部变量表中存放方法的局部变量。java源码被编译为Class文件时,某个方法的局部变量表的大小就已经确定在了该方法Code的属性中了 。

    局部变量表的容量以变量槽 variable slot 为最小单位。

    java虚拟机规范规定,变量槽能存放下一个byte,int ,char,reference,等8种数据类型。这8种数据类型除过reference都是没有超过32位的。reference是一个对象的引用,虚拟机没有明确规定其长度,其是32还是64位取决于虚拟机本身是32位还是64位的。

    java虚拟机中明确规定的64位长度的数据类型只有long和double,当存储这个类型的变量时,会分配2个连续的变量槽来存储。由于局部变量表是虚拟机栈中的,是线程的私有变量,因此无论读写2个连续的变量槽是否是原子操作,都不会引起数据竞争和线程安全问题。

    对于使用2个相邻的变量槽来存在一个64位数据,java虚拟机不允许以任何方式单独访问其中的一个变量槽,必须2个一起访问。

    局部变量和之前的类变量有一个很大的差异:

    类的字段变量有2次赋初值的过程:准备阶段会赋初值,初始化阶段,赋予程序代码定义的初始值。因此,在写代码时,即使没有为类变量赋初值也是可以使用的,因为在准备阶段会自动为其赋一个初值。

    局部变量如果在程序里没有赋值,则是完全不能使用的。

    3.操作数栈

    顾名思义,操作数栈是一个后进先出的栈结构。同局部变量表一样,栈的最大深度也在编译的时候就写入了方法的Code属性里了。在方法执行的任何时候,栈的深度都不会超过这个最大值。操作数栈中的元素可以是任意的java数据类型。其中,32位数据类型所占的栈容量是1,64位操作数栈所占的栈容量是2. 计算的时候,会将数据出栈,计算完后,再将结果入栈。所计算的数据类型必须严格与字节码的序列匹配。比如iadd操作时,最接近栈顶的两个元素必须是int类型。

    4.动态连接

    字节码文件中的常量池中存在大量的符号引用,字节码中的方法调用就以常量池中的符号引用作为参数。这些符号引用一部分会在类加载或第一次使用的时候被转换为直接引用,这种转换称为静态解析。另外一部分会在每一次运行期间都转换为直接引用,这部分就称为动态连接。栈帧中的动态连接,就是一个指向运行时常量池中该栈帧所属方法的引用,该引用就是为了支持方法调用过程中的动态连接。

    5.方法返回地址

    一个方法开始执行中,只有2种方式可以退出这个方法。一种是正常退出,即执行引擎碰到任意一个方法返回的字节码指令,就会将返回值(如果有返回值)返回给上层调用者。另外一种是 异常调用完成。异常退出的方法是不会给上层调用者返回任何返回值的。

    方法的退出就对应着虚拟机栈中的栈帧出栈。调用对应着入栈。

    展开全文
  • JAVA栈帧

    2017-02-18 23:32:00
    简介 Java栈是一块线程私有的内存空间。java堆和程序数据相关,java栈就是和线程执行密切相关的,线程的执行的基本行为是函数调用,每次函数调用的...每一次函数调用都有一个对应的栈帧被压入java栈。每一个函数调...

    简介

    Java栈是一块线程私有的内存空间。java堆和程序数据相关,java栈就是和线程执行密切相关的,线程的执行的基本行为是函数调用,每次函数调用的数据都是通过java栈来传递的。

    Java栈与数据结构中的stack有着类似的含义,都是先进先出的数据结构,只支持出栈和入栈操作。java栈中保存的主要内容为栈帧。每一次函数调用都有一个对应的栈帧被压入java栈。每一个函数调用结束,都会有一个栈帧被弹出java栈。当前正在执行的函所对应的栈帧位于当前栈的栈顶,它保存当前函数的局部变量,中间运算结果等数据。

    当函数返回时,栈帧从java栈中被弹出。java方法有两种返回的方式,
    * 一种是正常的函数返回;
    * 一种是异常抛出。
    这两种方式都会导致栈帧被弹出。

    一个栈帧中至少要包含局部变量表、操作数栈和帧数据几个部分。

    由于每个函数调用都会生成对应的栈帧,从而占用一定的栈空间,因此,如果栈空间不足,那么函数调用自然就无法继续进行下去,特别是递归算法特别需要耗费栈帧。当请求的栈的深度大于最大的可用栈的深度时,系统就会抛出StackOverflowError栈溢出错误。

    两大著名的程序猿交流网站的名称的由来,StackOverflowSegmentFault,就是这么来的。

    程序实验

    下面的例子通过递归调用,打印出我的电脑中可以用的最大的栈深。

    public class PrintStackDeepth {
        private static int length=0;
        public static void recursion()
        {
        length++;
        recursion();
        }
        public static void main(String[] args) {
        try {
            recursion();
        } catch (Throwable ex) {
            System.out.println(length);
        }
        }
    }

    运行说明

    再制定当前程序运行的最大内存的情况下,不同的-Xss值会获得不同的栈深

    Java程序中,每个线程都有自己的Stack Space。这个Stack Space不是来自Heap的分配。所以Stack Space的大小不会受到-Xmx和-Xms的影响,这2个JVM参数仅仅是影响Heap的大小。
    Stack Space用来做方法的递归调用时压入Stack Frame。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出StackOverflow的错误。Stack Space的大小随着OS,JVM以及环境变量的大小而发生变化。一般说来默认的大小是512K。在64位的系统中,这个Stack Space值会更大。一般说来,Stack Space为128K是够用的。这时你说需要做的就是观察。如果你的程序没有爆出StackOverflow的错误,可以使用-Xss来调整Stack Space的大小为128K。(eg:-Xss128K)

    转载于:https://www.cnblogs.com/shugen/p/6862983.html

    展开全文
  • java 栈帧

    2019-09-19 18:13:54
    帧(StackFrame): 是用于支持虚拟机进行方法调用和方法执行的数据结构,...栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟...

    帧(StackFrame):

    是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(VirtualMachineStack)的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

    局部变量表:用于存储方法参数和方法内的局部变量的变量值存储空间变量。在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。 局部变量表的容量以变量槽(VariableSlot,下称Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小.

    操作数栈: 它是一个后入先出(LastInFirstOut,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。

    动态链接: 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.

    方法返回地址: 无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

    当执行引擎遇到了方法返回的字节码命令,或者执行过程中发生了一个异常,并且没有在方法内得到处理

    转载于:https://my.oschina.net/u/242676/blog/1827334

    展开全文
  • java栈帧

    2017-03-31 17:20:00
    栈帧:局部变量表:保存函数的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。操作数栈:主要用于保存计算过程的中间结果,同时...
  • Java 栈帧

    千次阅读 2016-05-01 12:44:47
    结构: 1. 局部变量表:一组变量值的存储空间,用于存放方法参数和方法内的局部变量,编译完成后大小确定,最基本的...Java属于 静态多分派,动态单分派 : 重载能调用父类和子类的方法,重写只能调用子类的方法
  • 标题:jvm学习第四天—栈帧(Java虚拟机的数据都以栈帧的格式存在) 学习内容: 1、栈帧的内部结构 2、 局部变量表 3、 操作数栈 4、 动态链接( 或指向运行时常量池的方法引用) 5、方法返回地址(或方法正常退出或者...
  • java栈帧

    千次阅读 2019-06-20 11:27:14
    获取栈帧 StackTraceElement[] stacks = Thread.currentThread().getStackTrace(); stacks[0].getMethodName();...StackTraceElement其实就是Java线程转储之后的栈帧,可以通过栈帧获取方法名称等。Java打...
  • java栈帧结构

    2017-08-06 20:29:11
    运行时栈帧结构 栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素。每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。 每一个栈帧在编译程序...
  • Java虚拟机——栈帧

    2020-01-13 13:13:47
    栈帧(Stack Frame)存储于Java栈中,栈帧中负责存储局部变量表、操作数栈、动态链接和方法返回值等信息。 栈帧是一种用于支持JVM调用/执行程序方法的数据结构,每一个方法从开始调用到执行结束返回的过程对应着...
  • java 栈帧 虚拟机栈

    2017-04-10 11:22:15
    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。...
  • Java虚拟机之栈帧

    2019-01-31 15:49:00
    写在前面的话:Java虚拟机是一门学问,是众多Java大神们的杰作,由于我个人水平有限,精力有限,不能保证所有的东西都是正确的,这里内容都是经过深思熟虑的,部分引用原著的内容,讲的已经很好了,不在累述。...
  • 我们的Java运行时数据区包括线程独占区中的Java虚拟机栈,Java虚拟机栈中的“元素”就是栈帧。     什么是栈帧栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时...
  • java 栈和栈帧

    2018-02-05 15:57:00
    java 栈和栈帧 文章转载自:http://www.tuicool.com/articles/URZrMnb   jvm为每个新创建的线程都分配一个堆栈。堆栈以帧为单位保存线程的状态。jvm对堆栈只进行两种操作:以帧为单位的压栈和出栈...
  • JAVA虚拟机结构之栈帧

    千次阅读 2018-09-12 21:20:33
    最顶部的栈帧称为当前栈帧栈帧所关联的方法称为当前方法,定义这个方法的类称为当前类,该线程中,虚拟机有且也只会对当前栈帧进行操作。 栈帧的作用有存储数据,部分过程结果,处理动态链接,方法返回值和异常.....
  • Java 栈和栈帧

    2019-01-22 11:39:54
    jvm为每个新创建的线程都分配一个堆栈。堆栈以帧为单位保存线程的状态。...栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machin...
  • 前言 java虚拟机是java跨平台的基石,本文的描述以jdk7.0为准,其他...每当一个java方法被执行时都会在虚拟机中新创建一个栈帧,方法调用结束后即被销毁。 栈帧存储空间为虚拟机栈,每一个栈帧都有自己的局部变量...
  • java虚拟机栈-栈帧

    2020-12-31 13:35:08
    3. 动态链接(Dynamic Linking)(或执行"运行时常量 池"的方法引用)----深入理解Java多态特性必读!! 4. 方法返回地址(Return Adress)(或方法正常退出或者异常退出的定义) 5. 一些附加信息 其中部分参考书目上...
  • 文章目录Java结合方法栈帧理解递归编程思想递归的介绍阶乘——递归入门案例递归和方法栈尾递归优化汉诺塔实现 Java结合方法栈帧理解递归编程思想 递归的介绍 In computer programming, the term recursive ...
  • 一篇文章快速搞懂Java虚拟机的栈帧结构

    万次阅读 多人点赞 2020-01-04 10:04:19
    栈帧Java虚拟机中的虚拟机栈的基本元素,它也是用于支持Java虚拟机进行方法调用和方法执行背后的数据结构,了解了它就可以更好地理解Java虚拟机执行引擎是如何运行的。
  • 栈帧

    2017-12-29 19:58:36
    2、栈帧所在的内存模型为java运行时数据区的虚拟机栈。 3、虚拟机栈(vm stack)为线程私有,故栈帧也为线程私有。 4、一个线程的方法调用链很长,很多方法都处于执行状态,但仅有栈顶的栈帧才是有效栈帧
  • Java 虚拟机以方法作为最基本的执行单元,“ 栈帧 ” (Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。...
  • java虚拟机栈帧的结构

    2016-09-07 18:56:53
    2)局部变量表,一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,在Java程序编译为Class文件时,就在方法的Code属性中max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,575
精华内容 1,830
关键字:

栈帧java

java 订阅