`
jiasanshou
  • 浏览: 16184 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java栈和栈桢

阅读更多

原文应该也是在JavaEye中的博客,找不到了。

在原文基础了做了增删,以方便理解。

Java中的栈

每当线程调用一个Java方法时,JVM就会在该线程对应的栈中压入一个帧,这个帧自然就成了当前帧。当执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等。

Java栈上的所有数据都是私有的。任何线程都不能访问另一个线程的栈数据。所以我们不用考虑多线程情况下栈数据访问同步的情况。

像方法区和堆一样,Java栈和帧在内存中也不必是连续的,帧可以分布在连续的栈里,也可以分布在堆里

Java栈的组成元素——栈帧

栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,他们是按字长计算的。但调用一个方法时,它从类型信息中得到此方法局部变量区和操作数栈大小,并据此分配栈内存,然后压入Java栈。

局部变量区 局部变量区被组织为以一个字长为单位、从0开始计数的数组,类型为short、byte和char的值在存入数组前要被转换成int值,而long和double在数组中占据连续的两项,在访问局部变量中的long或double时,只需取出连续两项的第一项的索引值即可,如某个long值在局部变量区中占据的索引时3、4项,取值时,指令只需取索引为3的long值即可。

Java虚拟机规范第二版 写道
3.6.1 Local Variables 本地变量

Each frame (§3.6) contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame (§4.7.3).
每个栈帧都包含以数组形式保存的变量,称为本地变量。这个数组的长度在编译时已经知道了,并且会被记录在类或接口的对应方法的二进制编码(bytecode)中。
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
本地变量在数组中有两种实现形式,数组中的一个或数组中的一对值。数组中的一个值,可以持有boolean, byte, char, short, int, float,引用,返回地址(void return)。数组中的一对值可以持有一个long或double类型的值。关于这一点下面有更详细的介绍和举例。
Local variables are addressed by indexing. The index of the first local variable is zero. An integer is be considered to be an index into the local variable array if and only if that integer is between zero and one less than the size of the local variable array.
本地变量通过索引(indexing)寻址。既然是数组,显然是以0开始,索引的具体值使用int类型实现且在0和数组长度之间,但是不包含最后的长度(less than)。明显的事情,数组的取值应该是0~(length-1).
A value of type long or type double occupies two consecutive local variables. Such a value may only be addressed using the lesser index. For example, a value of type double stored in the local variable array at index n actually occupies the local variables with indices n and n +1; however, the local variable at index n +1 cannot be loaded from. It can be stored into. However, doing so invalidates the contents of local variable n.
long或double类型的值占用2个连续的数组值。但是这个值在寻址的时候可能使用的是较小的那个index.比如,一个long类型的值100L.在局部变量的数组中的索引值是n和n+1.寻址的时候使用的是较小的n,而不是n+1.n+1不能被载入,但是可以存入,但是这样的话,100L这个值就无效了。
The Java virtual machine does not require n to be even. In intuitive terms, values of types double and long need not be 64-bit aligned in the local variables array. Implementors are free to decide the appropriate way to represent such values using the two local variables reserved for the value.
但是JVM并不强制要求这么做。不同的JVM实现者可以自由的选择实现方式,比如用2个变量存储。
The Java virtual machine uses local variables to pass parameters on method invocation. On class method invocation any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1.
JVM用本地变量在方法调用时传参,传参时有2种方式:类方法(声明为static的方法)和实例方法(非static方法及构造器等)。类方法传入的参数索引以0开始。实例方法则不然,实例方法默认传入一个this引用作为第一个参数,它的index就是0.其余方法从1开始。

 

下面就看个例子,好让大家对局部变量区有更深刻的认识。这个图来自《深入JVM》:

  1. public static int runClassMethod(inti,longl,floatf,doubled,Objecto,byteb){
  2. return0;
  3. }
  4. public int runInstanceMethod(charc,doubled,shorts,booleanb){
  5. return0;
  6. }

上面代码片的方法参数和局部变量在局部变量区中的存储结构如下图:

局部变量区的存储结构

上面这个图没什么好说的,大家看看就会懂。但是,在这个图里,有一点需要注意:

runInstanceMethod的局部变量区第一项是个reference(引用),它指定的就是对象本身的引用,也就是我们常用的this,但是在runClassMethod方法中,没这个引用,那是因为runClassMethod是个静态方法。

操作数栈和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。可把操作数栈理解为存储计算时,临时数据的存储区域。下面我们通过一段简短的程序片段外加一幅图片来了解下操作数栈的作用,以及操作数栈与本地变量区的状态变化。

int a = 100;

int b = 98;

int c = a+b;

字节码序列:

iload_0//入栈

iload_1//入栈

iadd//弹出2个栈中数据相加后,把结果入栈

istore_2 //弹出结果存储到局部变量中位置2处

操作数栈的结构

从图中可以得出:操作数栈其实就是个临时数据存储区域,它是通过入栈和出栈来进行操作的。

帧数据区除了局部变量区和操作数栈外,Java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在Java栈帧的帧数据区中。
当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。

除了处理常量池解析外,帧里的数据还要处理Java方法的正常结束和异常终止。如果是通过return正常结束,则当前栈帧从Java栈中弹出,恢复发起调用的方法的栈。如果方法又返回值,JVM会把返回值压入到发起调用方法的操作数栈。

为了处理Java方法中的异常情况,帧数据区还必须保存一个对此方法异常引用表的引用。当异常抛出时,JVM给catch块中的代码。如果没发现,方法立即终止,然后JVM用帧区数据的信息恢复发起调用的方法的帧。然后再发起调用方法的上下文重新抛出同样的异常。

栈的整个结构

在前面就描述过:栈是由栈帧组成,每当线程调用一个Java方法时,JVM就会在该线程对应的栈中压入一个帧,而帧是由局部变量区、操作数栈和帧数据区组成。那在一个代码块中,栈到底是什么形式呢?下面是我从《深入JVM》中摘抄的一个例子,大家可以看看:

代码片段:

 

public class JVMStack {

    public static void addAndPrint() {
        double result = addTwoTypes(1, 88.88);
        System.out.println(result);
    }

    public static double addTwoTypes(int i, double d) {
        return i + d;
    }

}
 该类对应的ByteCode,使用eclipse bytecode outline插件查看

 

 

  public static addAndPrint()V
   L0
    ICONST_1 //常量入参1入栈 push 1 onto the stack
    LDC 88.88 //88.88入栈
    INVOKESTATIC com/alipay/test/JVMStack.addTwoTypes(ID)D //调用方法addTwoTypes
    DSTORE 0 //将结果出栈并保存到本地变量数组
   L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    DLOAD 0
    INVOKEVIRTUAL java/io/PrintStream.println(D)V
   L2
    RETURN

  public static addTwoTypes(ID)D //新的方法有新的栈帧
   L0
    ILOAD 0 //第一个入参入栈
    I2D //int转换为double
    DLOAD 1 //第二个参数入栈
    DADD //执行double 加法运算
    DRETURN //返回
}
 

 

 

执行过程中的三个快照:

 

 

上面所给的图,只想说明两件事情,我们也可用此来理解Java中的栈:

1、只有在调用一个方法时,才为当前栈分配一个帧,然后将该帧压入栈。

2、帧中存储了对应方法的局部数据,方法执行完,对应的帧则从栈中弹出,并把返回结果存储在调用方法的帧的操作数栈中。

 

Questions:

1栈桢由那三部分组成,各自存储了什么数据?

2 操作指令在栈桢中是如何工作的?

3 如果发生零除异常,它是如何被抛出的?

 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics