JVM 栈帧

 一、栈帧

栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。

栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间分配在Java虚拟机栈之中,每一个栈帧都有自己的局部变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用。下载

局部变量表和操作数栈的容量是在编译期确定,并通过方法的Code属性保存及提供给栈帧使用。因此,栈帧容量的大小仅仅取决于Java虚拟机的实现和方法调用时可被分配的内存。

在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈帧(Current Frame),这个栈帧对应的方法就被称为是当前方法(Current Method),定义这个方法的类就称作当前类(Current Class)。对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的对局部变量表和操作数栈进行的操作。

如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。当一个新的方法被调用,一个新的栈帧也会随之而创建,并且随着程序控制权移交到新的方法而成为新的当前栈帧。当方法返回的之际,当前栈帧会传回此方法的执行结果给前一个栈帧,在方法返回之后,当前栈帧就随之被丢弃,前一个栈帧就重新成为当前栈帧了。下载

栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧。

二、局部变量表

每个栈帧内部都包含一组称为局部变量表(Local Variables)的变量列表。栈帧中局部变量表的长度由编译期决定,并且存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用。

一个局部变量(Slot)可以保存一个类型为boolean、byte、char、short、float、reference和returnAddress的数据,两个局部变量可以保存一个类型为long和double的数据。

局部变量使用索引来进行定位访问,第一个局部变量的索引值为零,局部变量的索引值是从零至小于局部变量表最大容量的所有整数。下载

long和double类型的数据占用两个连续的局部变量,这两种类型的数据值采用两个局部变量之中较小的索引值来定位。例如我们讲一个double类型的值存储在索引值为n的局部变量中,实际上的意思是索引值为n和n+1的两个局部变量都用来存储这个值。索引值为n+1的局部变量是无法直接读取的,但是可能会被写入,不过如果进行了这种操作,就将会导致局部变量n的内容失效掉。

上文中提及的局部变量n的n值并不要求一定是偶数,Java虚拟机也不要求double和long类型数据采用64位对齐的方式存放在连续的局部变量中。虚拟机实现者可以自由地选择适当的方式,通过两个局部变量来存储一个double或long类型的值。

Java虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用的时候,它的参数将会传递至从0开始的连续的局部变量表位置上。特别地,当一个实例方法被调用的时候,第0个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即Java语言中的“this”关键字)。后续的其他参数将会传递至从1开始的连续的局部变量表位置上。下载

三、操作数栈

每一个栈帧内部都包含一个称为操作数栈(Operand Stack)的后进先出(Last-In-First-Out,LIFO)栈。栈帧中操作数栈的长度由编译期决定,并且存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用。

在上下文明确,不会产生误解的前提下,我们经常把“当前栈帧的操作数栈”直接简称为“操作数栈”。

操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的。Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果。

举个例子,iadd字节码指令的作用是将两个int类型的数值相加,它要求在执行的之前操作数栈的栈顶已经存在两个由前面其他指令放入的int型数值。在iadd指令执行时,2个int值从操作栈中出栈,相加求和,然后将求和结果重新入栈。在操作数栈中,一项运算常由多个子运算(Subcomputations)嵌套进行,一个子运算过程的结果可以被其他外围运算所使用。下载

在操作数栈中的数据必须被正确地操作,这里正确操作是指对操作数栈的操作必须与操作数栈栈顶的数据类型相匹配,例如不可以入栈两个int类型的数据,然后当作long类型去操作他们,或者入栈两个float类型的数据,然后使用iadd指令去对它们进行求和。有一小部分Java虚拟机指令(例如dup和swap指令)可以不关注操作数的具体数据类型,把所有在运行时数据区中的数据当作裸类型(Raw Type)数据来操作,这些指令不可以用来修改数据,也不可以拆散那些原本不可拆分的数据,这些操作的正确性将会通过Class文件的校验过程来强制保障。

在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度。

分析:

有如下代码:下载

Java代码

  1. package cc.lixiaohui.demo;
  2. public class Foo {
  3. public static void main(String[] args) {
  4. int a = 1;
  5. int b = 2;
  6. int c = a + b;
  7. }
  8. }

利用javap工具生成虚拟机汇编代码:

括号内的注释我是自己加的,其余是javap生成的

Java代码

  1. E:\EclipseWorkspace\demo-foo\target\classes\cc\lixiaohui\demo>javap -c -l Foo.class
  2. Compiled from "Foo.java"
  3. public class cc.lixiaohui.demo.Foo {
  4. public cc.lixiaohui.demo.Foo();   (//这是默认构造方法)
  5. Code:
  6. 0: aload_0
  7. 1: invokespecial #8                  // Method java/lang/Object."<init>":()V
  8. 4: return
  9. LineNumberTable:
  10. line 3: 0
  11. LocalVariableTable:
  12. Start  Length  Slot  Name   Signature
  13. 0       5     0  this   Lcc/lixiaohui/demo/Foo;
  14. public static void main(java.lang.String[]);
  15. Code:
  16. 0: iconst_1     (//把int值1压入操作数栈)
  17. 1: istore_1     (//把栈顶值存储到局部变量表下标为1的位置)
  18. 2: iconst_2     (//把int值2压入操作数栈)
  19. 3: istore_2     (//把栈顶值存储到局部变量表下标为2的位置)
  20. 4: iload_1      (//取局部变量表中下标为1的变量压栈)
  21. 5: iload_2      (//取局部变量表中下标为2的变量压栈)
  22. 6: iadd         (//从操作数栈中弹出两个int值进行相加操作,相加的结果压栈)
  23. 7: istore_3     (//把栈顶值存储到局部变量表下标为3的位置)
  24. 8: return
  25. LineNumberTable: (//这是java代码行与该栈帧指令代码行的映射)
  26. line 5: 0        (//java代码第五行为“int a = 1”,对应的虚拟机汇编代码为“iconst_1”)
  27. line 6: 2
  28. line 7: 4
  29. line 8: 8
  30. LocalVariableTable:
  31. Start  Length  Slot  Name   Signature
  32. 0       9     0  args   [Ljava/lang/String;
  33. 2       7     1     a   I
  34. 4       5     2     b   I
  35. 8       1     3     c   I
  36. }

可以看到执行过程中不断在局部变量表和操作数栈间来回传递数据。

时间: 2024-10-08 04:10:55

JVM 栈帧的相关文章

[jvm解析系列][十一]字节码执行之栈帧,你的字节码是如何运行的?

在之前的章节中我们讲解了jvm的内存分配和管理,class的文件结构,就差之行了.那么从第十一章开始我们就开始讲java虚拟机是如何执行一个class文件的. 首先我们应该明确虚拟机是区别于物理机的一种说法,物理机的执行引擎是建立在处理器,硬件 ,指令集之上的.而我们的虚拟机则由自己实现.在虚拟机中大致分为两种执行方式:解释执行和编译执行. 我们之前讲过,虚拟机运行方法的时候运行在java虚拟机栈里面,里面的存储结构是栈帧,需要了解一个虚拟机如何运行字节码文件的,首先我们需要了解一个栈帧的结构.

Jvm(59),虚拟机字节码执行引擎----运行时栈帧结构

后面讲的所有的东西就是对前面所总览的虚拟机栈的进一步理解. 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)[1]的栈元素.栈帧存储了方法的局部变量表.操作数栈.动态连接和方法返回地址等信息.每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程. 每一个栈帧都包括了局部变量表.操作数栈.动态连接.方法返回地址和一些额外的附加信息.在编译程序代码的时候,

【深入浅出-JVM】(6):栈帧

代码 package com.mousycoder.mycode.happy_jvm; /** * @version 1.0 * @author: mousycoder * @date: 2019-06-11 15:45 */ public class TestStackDeep1 { private static int count = 0; public static void recursion(long a,long b,long c) { long e = 1, f= 2,g =3 ,

jvm栈-运行控制,jvm-堆运行存储共享单元

JVM-栈 2012-09-17 15:43:53 分类: Java 原文转自:http://www.blogjava.net/nkjava/archive/2012/03/15/371971.html JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放.放在哪儿,另外JVM堆中存的是对象.JVM栈中存的是基本数据类型和JVM堆中对象的引用. JVM基础概念:JVM堆与JVM栈 数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和

JVM栈内存详解

JVM栈之局部变量表:包含参数和局部变量 局部变量表存放了基本数据类型.对象引用和returnAddress类型(指向一条字节码指令的地址).其中64位长度的long和double类型的数据会占用2个局部变量空间(slot)(下图1到3的原因),其余数据类型只占用1个.局部变量表所需的内存空间在编译期间完成分配.每个方法都对应一个栈帧. public class StackDemo { //静态方法 public static int runStatic(int i, long l, float

内存区域---jvm 栈

一.栈的含义 用于描述方法执行的动态内存模型,简单来说就是代码中的方法模块.主要用于局部变量表.栈的大小在编译器中就已经分配了空间,在进入一个方法时,这个方法需要栈帧分配多少内存是固定的,运行期间是不会改变大小. 局部变量表示指:存放编译期可知的各种基本类型和引用类型. 二.栈帧 说到了栈,就不得不说栈中最为重要的东西,栈帧.什么是栈帧,我们先来看看比较官方的定义:程序执行时,调用每个方法执行时,都会为其创建一个栈帧,栈帧伴随着方法从创建到执行完成.看了这段话是不是还是很难理解,接下来我们就用一

JVM 栈堆和方法区

栈区 栈区描述的是方法执行的内存模型.每个方法在执行时都会创建一个栈帧(存储局部变量.操作数栈.动态链接.方法出口等) JVM为每个线程创建一个栈,栈属于线程私有,不能实现线程间的共享,用于存放该线程执行方法的信息(实际参数.局部变量等) 基本类型变量去,执行环境上下文,操作指令区(存放操作指令) 堆区 堆用于存储创建好的对象和数组(数组也是对象) JVM只有一个堆,被所有线程共享 堆是一个不连续的内存空间,分配灵活,速度慢! 方法区 Jvm只有一个方法区,被所有线程公用 存放整个程序中唯一的元

程序分析(栈帧问题)

如下程序,在主函数中并未调用,但为何会重启? 在主函数中,调用了fun1的函数,而在fun1函数中,p--后,将栈帧上p地址上的内容修改成了fun 的地址,因此,程序运行到fun函数中. 2. 修改栈帧上变量的内容 (不通过变量名) 测试结果: 在fun函数中,b先压栈,a后压栈,变量b在变量a的上面.

【链接】函数栈帧

本节通过反汇编可执行文件得到的文件,研究函数栈帧的相关内容: 栈帧整体示意图如下 示例代码 #include <stdio.h> #include <iostream> using namespace std; int z = 10; int add(int x, int y) { return x+y+z; } int inc20(int x) { int y = 10; return add(x, y); } int main(void) { int a = 30; a = i