java编程

java方法栈操作以及局部变量示意图

字号+ 作者:风潇潇 来源:原创 2016-08-10 11:45 我要评论( )

这篇文章是解析底层怎么执行指令的。不是原创。不过我觉得从书上看到好东西就得记到自己的博客上。做个积累

     java运行与之对应的就是栈操作。调一个方法就是压入一个栈帧,方法执行完就是弹出一个栈帧。下面来讲讲栈帧。
     栈帧由三部分组成:局部变量区、操作数栈和帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,它们是按字长计算的。
     这里解释一下什么是字长:每8位二进制数字构成一个字节,若干个字节构成一个字(具体取决于处理器),字的位数叫做字长,比如80X86中的十六位机,则一个字由两个字节构成,那么一个字的字长为16位。实际上,现在的个人电脑一般都是32位或以上的(比如64位)。简单来说就是:字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制位数,这可是基础知识哦。
     编译器在编译时就确定了这些值并放在class文件中。而数据区域的大小依赖于具体实现。
     当虚拟机调用一个Java方法时,它从对应的类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入Java栈。下面分别解释一下几个概念。
     局部变量区 .Java栈帧的局部变量区被组织为一个以字长为单位、从0开始计算的数组。字节码指令通过从0开始的索引来使用其中的数据。类型为int、float、refrerence、和returnAddress的值在数组汇总只占据一项,而类型为byte、short和char的值在存入数组钱都将被转换为int值,因而同样占据一项。但是类型为long和double值在数组中却连续占两项。而在访问局部变量中的long和double值得时候,指令只需指出连续两项中的第一项索引值。例如long占第3、4项,那么指令取索引为3的long值。
     局部变量区包含对应方法的参数和局部变量。编译器首先按申明的顺序吧这些参数放入局部变量数组。
   
     public static int runClassMethod(int i,long l,float f,double d,Object o,byte b){
        return 0;
    }
    
  public int runInstanceMethod(char c,double d,short s,boolean b){
        return 0;
    }

其中要注意,runInstanceMethod()中,局部变量中第一个参数是一个refrence(引用)类型。这个参数没有申明试的给出,但这个参数this对于任何一个实例方法都是隐含加入的,它用来表示调用该方法的对象本身。于此想法,方法runClassMethod()中就没有这个隐含的this变量,因为它使一个类方法。类方法只与类相关,而与具体的对象无关。
      在源代码的byte、short、char和boolean在局部变量区域都被转换成了int,在操作数栈中也一样。虚拟机并不直接支持boolean类型,因此Java编译器是用int来表示boolean。但Java虚拟机对byte、short和chart是直接支持的,这些类型的值可以作为实例变量或者数组元素存储在局部变量区,也可以作为类变量存储在方法区。但在局部变量区和操作数栈中都会被转换成int类型的值。他们在栈帧中的时候是当做int来处理的,只有被存回堆或方法区时,才会转换会原来的类型。
      同样注意,refrence的是引用传递,并且都存储在队中,永远不会作对象拷贝。
     除了java方法参数(编译器首先会严格按照它们的声明顺序放到局部变量的数组中,而对于真正的局部变量,它可以任意决定放置顺序,甚至可以用一个索引只带两个局部变量---比如当两个局部变量的作用域不重叠时,下如下代码)
   public void runTwoLoops(){
        for(int i=0;i<=10;i++){
            //code
        }
        for(int j=0;j<=10;j++){
            //code
        }
    }
   
操作数栈  和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈——来访问。比如某个指令把一个值亚茹到操作数栈中,稍微另一个指令就可以弹出这个值来使用。
     虚拟机在操作数栈中存储数据的方式和局部变量区中是一样的,如int、long、float、double、refrence和retuanType的存储,对于byte、short以及char类型在压入栈操作数栈钱,会被转换为int。
     不同于程序计数器,java虚拟机没有寄存器,程序计数器也无法被程序指令直接访问。Java虚拟机的指令是从操作数栈中而不是从寄存器中取得操作数,比如从字节码六中跟随在操作码之后的字节中或从常亮池中,但是主要还是从操作数栈中获得操作数。请看下面几条指令

在这个字节码序列里,前两条指令iload_0 和iload_1 将存储在局部变量中的索引为0 和 1的整数亚茹操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并存储到局部变量区索引为2的位置。看下图


当然上面讲述了调用一个方法以及怎么执行指令,怎么操作相关变量,怎么存储,还有个东西叫帧数据区域,这里的这些数据是用来保存常量解析、正常方法返回以及异常外派机制。暂时不说了

这里将:
==================================
public class AddAB{

public static void main(String[] args){
    int a=200;
    int b=300;
    int c=0;
    c=a+b;
        System.out.println(c);

}

}
这段代码的指令给贴粗来,具体请看注释。要注意的是。在代码中定义的变量都是先在栈中创建,再load进局部变量区里面,就完成了一个变量的定义

Compiled from "AddAB.java"
public class AddAB {
  public AddAB();
    Code:
       0: aload_0       
       1: invokespecial #1                
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: sipush        200 //压栈
       3: istore_1      //弹出  放入1位置
       4: sipush        300 //压栈
       7: istore_2      //弹出 //放入2位置
       8: iconst_0      //压栈
       9: istore_3      //弹出 放3位置
      10: iload_1       //将1位置压栈
      11: iload_2       //将2位置压栈
      12: iadd          // 相加
      13: istore_3      //弹出  放入3位置
      14: getstatic     #2                 
      17: iload_3       
      18: invokevirtual #3                  
      21: return        
}

=====================
代码:
public class AddAB{

public static void main(String[] args){
    int a=200;
    int b=300;
    int c=0;
    c=add(a,b);
        System.out.println(c);

}

public static int add(int a,int b){
    int c=a+b;
    return c;
}

}

指令:
Compiled from "AddAB.java"
public class AddAB {
  public AddAB();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: sipush        200
       3: istore_1      
       4: sipush        300
       7: istore_2      
       8: iconst_0      
       9: istore_3      
      10: iload_1       
      11: iload_2       
      12: invokestatic  #2                  // Method add:(II)I
      15: istore_3      
      16: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3       
      20: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
      23: return        

  public static int add(int, int);
    Code:
       0: iload_0       //压栈 0位置
       1: iload_1       //压栈 1位置
       2: iadd          //相加
       3: istore_2      //弹出2位置 这里为什么
       4: iload_2       //压入2位置
       5: ireturn       //返回
}
代码:
public int add(int a,int b){
    int c=0;
    try{
        c=a+b;
        return c;
    }finally{
    c=235;    
}    
}
指令:
  public int add(int, int);
    Code:
       0: iconst_0      //0 这个值压栈
       1: istore_3      //出栈存储到 3位置
       2: iload_1       // 1位置 压栈
       3: iload_2       // 2位置 压栈
       4: iadd          // 相加
       5: istore_3      //出栈存储到3位置
       6: iload_3       // 3位置 压栈
       7: istore        4 //存到4位置
       9: sipush        235 //235 压栈
      12: ireturn       //返回
      13: astore        5
      15: sipush        235
      18: ireturn       
    Exception table:
       from    to  target type
           2     9    13   any
          13    15    13   any
public int add(int, int);
    Code:
       0: iconst_0      //0 这个值压栈
       1: istore_3        //出栈存储到 3位置
       2: iload_1       // 1位置 压栈
       3: iload_2       // 2位置 压栈
       4: iadd          // 相加
       5: istore_3        //出栈存储到3位置 此时i的值为500
       6: iload_3         // 3位置 压栈
       7: istore        4 // 出栈放到4位置 备份将i放到4位置
       9: sipush        235 //压栈
      12: istore_3      // 出栈放到3位置 此时i的值为235
      13: iload         4 //将4位置压栈,此时栈里的值为 500
      15: ireturn       //返回
      16: astore        5
      18: sipush        235
      21: istore_3      
      22: aload         5
      24: athrow        
    Exception table:
       from    to  target type
           2     9    16   any
          16    18    16   any


另外来说,指令执行得看具体的执行顺序,有操作数和操作栈。比如一个if (i % 4 == 3) 他的指令
2 iload_0 // Push local var 0
3 iconst_4 // Push constant 4
4 irem // Calc remainder of top two operands
5 iconst_3 // Push constant 3
6 if_icmpne 13 // Jump if remainder not equal to 3

就是总的来说,一条指令执行可能不需要操作数,也有可能需要操作数。irem就是求余,将0位置的压入栈,将4压入栈。再求余放入栈中,再压入3 if_icmpne这条指令需要2个数,即会从栈里弹出两个数来比较,如果是true就跳到13行

看了上面的提示,你知道此函数应该返回什么值了吗?是不是很不可思议
java虚拟机有很多指令。可以去了解一下


摘自:深入Java虚拟机(原书第二版清晰版)

转载请注明出处。

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
网友点评
评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)