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)嵌套进行,一个子运算过程的结果可以被其他外围运算所使用。
每一个操作数栈的成员(Entry)可以保存一个Java虚拟机中定义的任意数据类型的值,包括long和double类型。
在操作数栈中的数据必须被正确地操作,这里正确操作是指对操作数栈的操作必须与操作数栈栈顶的数据类型相匹配,例如不可以入栈两个int类型的数据,然后当作long类型去操作他们,或者入栈两个float类型的数据,然后使用iadd指令去对它们进行求和。有一小部分Java虚拟机指令(例如dup和swap指令)可以不关注操作数的具体数据类型,把所有在运行时数据区中的数据当作裸类型(Raw Type)数据来操作,这些指令不可以用来修改数据,也不可以拆散那些原本不可拆分的数据,这些操作的正确性将会通过Class文件的校验过程来强制保障。
在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度。
分析:
有如下代码:
- package cc.lixiaohui.demo;
- public class Foo {
- public static void main(String[] args) {
- int a = 1;
- int b = 2;
- int c = a + b;
- }
- }
利用javap工具生成虚拟机汇编代码(java虚拟机指令集):
括号内的注释我是自己加的,其余是javap生成的
- E:\EclipseWorkspace\demo-foo\target\classes\cc\lixiaohui\demo>javap -c -l Foo.class
- Compiled from "Foo.java"
- public class cc.lixiaohui.demo.Foo {
- public cc.lixiaohui.demo.Foo(); (//这是默认构造方法)
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcc/lixiaohui/demo/Foo;
- public static void main(java.lang.String[]);
- Code:
- 0: iconst_1 (//把int值1压入操作数栈)
- 1: istore_1 (//把栈顶值存储到局部变量表下标为1的位置)
- 2: iconst_2 (//把int值2压入操作数栈)
- 3: istore_2 (//把栈顶值存储到局部变量表下标为2的位置)
- 4: iload_1 (//取局部变量表中下标为1的变量压栈)
- 5: iload_2 (//取局部变量表中下标为2的变量压栈)
- 6: iadd (//从操作数栈中弹出两个int值进行相加操作,相加的结果压栈)
- 7: istore_3 (//把栈顶值存储到局部变量表下标为3的位置)
- 8: return
- LineNumberTable: (//这是java代码行与该栈帧指令代码行的映射)
- line 5: 0 (//java代码第五行为“int a = 1”,对应的虚拟机汇编代码为“iconst_1”)
- line 6: 2
- line 7: 4
- line 8: 8
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 9 0 args [Ljava/lang/String;
- 2 7 1 a I
- 4 5 2 b I
- 8 1 3 c I
- }
可以看到执行过程中不断在局部变量表和操作数栈间来回传递数据。
相关推荐
JVM栈帧图示,包括局部变量,操作数栈,动态连接,返回地址等各项描述。
每一个 JVM 线程都拥有一个私有的 JVM 线程栈,用于存放当前线程的 JVM 栈帧(包括被调用函数的参数、局部变量和返回地址等)。如果某个线程的线程栈空间被耗尽,没有足够资源分配给新创建的栈帧,就会抛出 java....
34个生产案例,还原真实的 JVM 问题现场!程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,...
栈是线程私有的内存区域,每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接等信息;程序计数器是线程私有的,用于记录当前虚拟机正在执行的线程指令。 JVM生命周期:JVM的生命周期始于启动...
栈帧 局部变量表 操作数栈 动态连接 方法出口 本地方法栈 堆 方法区 JavaVirtualMachineError StackOverflowError OutOfMemoryError JVM PS:JVM部分参考了《深入理解Java虚拟机 - 第二版》(周志明). 个人认为《深入...
Java虚拟机(JVM)面试题(总结最全面的面试题!!!) 文章目录Java内存模型我们开发人员编写的Java代码是怎么让电脑认识的为什么说java是跨平台语言Jdk和Jre和JVM的区别说一下 JVM由那些部分组成,运行流程是什么...
第95讲 运行时栈帧结构 00:08:46 第96讲 局部变量表 00:20:48 第97讲 操作数栈 00:08:36 第98讲 动态连接 00:02:56 第99讲 方法返回地址和附加信息 00:03:24 第100讲 方法调用-解析调用 00:09:49 ...
JVM 内存区域 线程私有 程序计数器 当前线程所执行的字节码的行号指示器 对于 Java 方法,记录正在执行的虚拟机字节码指令的地址;对于 native 方法,记录值为空 (Undefined) 唯一一个Java 虚拟机规范中没有规定...
Java虚拟机栈也是线程私有的,它的生命周期与线程...(当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈) 3. Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。
第95节运行时栈帧结构00:08:46分钟 | 第96节局部变量表00:20:48分钟 | 第97节操作数栈00:08:36分钟 | 第98节动态连接00:02:56分钟 | 第99节方法返回地址和附加信息00:03:24分钟 | 第100节方法调用-解析调用00:...
栈:栈帧(对象的引用,方法) 类加载 执行方法 创建对象 堆空间分代划分 outOfMemory异常 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 可以看jvm参数查看网址 Direct buffer memory : ...
/ 112 5.2.5 服务器JVM进程崩溃 / 113 5.3 实战:Eclipse运行速度调优 / 114 5.3.1 调优前的程序运行状态 / 114 5.3.2 升级JDK 1.6的性能变化及兼容问题 / 117 5.3.3 编译时间和类加载时间的优化 / 122 5.3.4 ...
深入理解JVM一、什么是JVM二、JAVA的运行机制三、JVM架构图四、类加载器子系统1、类加载器子系统作用2、加载(Loading)3、链接(Linking)3.1 验证 (Verify)3.2 准备(Prepare)3.3 解析(Resolve)3、初始化4、...
Jvm运行时,内存区域可以划分为两大部分 1.线程私有(程序计数器,虚拟机栈,本地方法栈) 2.线程共享(堆与方法区) 程序计数器:因为cpu会划分为时间片给多个线程执行,所以需要程序计数器记录下指令执行到具体的...
通俗的来讲,jvm主要分为5个部分 程序计数器、虚拟机枝、本地方法枝、 Java 堆、 方法区, 引用大佬总结的概括程序计数器用于存放下一条运行的指令,虚拟机栈和本地方法栈用于存放函数调用堆栈信息, Java 堆用于...
1.遇到new指令 1.虚拟机栈(存储指针引用)、2.元空间方法区(存储类 1.虚拟机栈 (栈帧中的本地方法表) 中引用的对象 2.方法区中类静态属性引用的对象
java8 rt.jar源码 layout ...//一个方法对应一块栈帧内存区域 int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math
JVM的重要性不言而喻,这个是学习JVM是看视频和读《深入理解JVM》时做的一些笔记,用于复习参考。 读书笔记 第2章:java内存模型和内存溢出异常 1.运行时数据区域 1.程序计数器:线程私有 2.java虚拟机栈:线程私有...