JVM运行时栈帧

简介: 在JVM中,每个线程都包含n个栈帧,每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

网络异常,图片无法展示
|


前言


在JVM中,每个线程都包含n个栈帧,每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

栈帧的生命周期随着方法的创建而创建,随着方法的结束而销毁,无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算方法的结束。

在某条线程执行过程中的某个时间点上,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧称为当前栈帧,这个栈帧对应的方法称为当前方法,定义这个方法的类称为当前类对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的局部变量表和操作数栈所进行的操作。

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

网络异常,图片无法展示
|

局部变量表

局部变量表(Local Variables Table)是一组变量值的存储空间,用于存放方法参数方法内部定义的局部变量

存储方法

局部变量表的容量以变量槽(Variable Slot)为最小单位,一般在虚拟机中,一个Slot占用32位存储空间(这不是固定的,虚拟机可以自行改变每个槽占用空间的大小,但一般都是32位)。

Java虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的变量槽数量。如果访问的是32位数据类型的变量,索引N就代表了使用第N个变量槽,如果访问的是64位数据类型的变量,则说明会同时使用第N和N+1两个变量槽。

eg:

在Java中,long在内存占64位,所以局部变量表用2个slot来存储

网络异常,图片无法展示
|

对于两个相邻的共同存放一个64位数据的两个变量槽,虚拟机不允许采用任何方式单独访问其中的某一个,《Java虚拟机规范》中明确要求了如果遇到进行这种操作的字节码序列,虚拟机就应该在类加载的校验阶段中抛出异常。

long和double的非原子性协定

在Java内存模型中,对于64位的数据类型(long和double),在模型中特别定义了一条宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性,这就是所谓的 “long和double的非原子性协定”(Non-Atomic Treatment of doubleand long Variables)

虽然有这个协定,但是,由于局部变量表(Local Variable Table)是建立在线程堆栈中的,属于线程私有的数据,无论读写两个连续的变量槽是否为原子操作,都不会引起数据竞争和线程安全问题

初始值问题

我们已经知道类的字段变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的初始值。

局部变量就不一样了,如果一个局部变量定义了但没有赋初始值,那它是完全不能使用的。所以不要认为Java中任何情况下都存在诸如整型变量默认为0、布尔型变量默认为false等这样的默认值规则。

eg:

// 这个方法会报:
// Error:(12, 28) java: variable y might not have been initialized
public class JVMTest {
    public static void main(String[] args) {
        int y;
        int z=3;
        System.out.println(y+z);
    }
}
// 这个会正常输出 3; 因为int的初始值为0
public class JVMTest {
    private static int y;
    public static void main(String[] args) {
        int z=3;
        System.out.println(y+z);
    }
}

操作数栈

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

eg:

public class JVMTest {
    public static void main(String[] args) {
        long y=9223372036854775800L;
        int z=2;
        long x=y+z;
    }
}

我们用javap -verbose JVMTest来查看他的class文件的字节码指令

网络异常,图片无法展示
|

在操作栈中的流程大致为:

网络异常,图片无法展示
|

动态链接

每个栈帧都包含一个指向当前方法所在类型的运行时常量池的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。在Class文件里,一个方法若要调用其他方法,或者访问成员变量,则需要通过符号引用(symolic reference)来表示,动态链接的作用就是将这些以符号引用所表示的方法转换为实际方法的直接引用。

什么是符号引用?

网络异常,图片无法展示
|

通过查看字节码,上面的#7#8#9等等都是符号引用,他在class文件里只是个符号,就像你定义一个变量名称一样,变量名只是和字符符号,并不是真正的指向内存的地址指针。这些符号都指向运行时常量池的引用。

方法返回地址

Java在调用方法时,只有两种返回方法,一种是正常返回,一种是异常返回

正常返回

正常返回指的就是在执行方法时,中间并没有异常抛出,或者已正确处理抛出的异常,这时就称当前方法正常调用完成,如果有返回值,就会给他调用者返回一个值,如果没有返回值(void)就正常返回。

这种场景下,当前栈帧承担着恢复调用者状态的责任,包括恢复调用者的局部变量表和操作数栈,以及正确递增程序计数器,以跳过刚才执行的调用方法指令等。 调用者的代码在被调用方法的返回值压入调用者栈帧的操作数栈后,会正常执行。

异常返回

在调用一些方法时,一些异常没有被正确捕获,就会导致方法终止,此时称方法异常调用完成,那一定不会有方法返回值返回给其调用者。

无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。

怎么理解这个必须返回到最初方法被调用时的位置呢?

eg:

网络异常,图片无法展示
|

上面异常是在13行发生的,但是它并没有停在13行,而是回到了最初调用它第10行的位置。

相关文章
|
2月前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
3天前
|
监控 算法 Java
JVM调优---堆溢出,栈溢出的出现场景以及解决方案
【7月更文挑战第3天】堆溢出(Heap Overflow)和栈溢出(Stack Overflow)是两种常见的内存溢出问题,通常发生在内存管理不当或设计不合理的情况下
10 3
|
14天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
23 2
|
18天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
26 4
|
2月前
|
监控 Java 测试技术
滚雪球学Java(45):探秘Java Runtime类:深入了解JVM运行时环境
【5月更文挑战第20天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 1
滚雪球学Java(45):探秘Java Runtime类:深入了解JVM运行时环境
|
20天前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
41 3
|
25天前
|
存储 缓存 安全
JVM(三)-运行时数据区(栈、程序计数器)
JVM(三)-运行时数据区(栈、程序计数器)
15 2
|
1月前
|
存储 Java 编译器
【JavaEE初阶】 JVM 运行时数据区简介
【JavaEE初阶】 JVM 运行时数据区简介
|
2月前
|
安全 Java
【JVM】运行时数据区、程序计数器
【JVM】运行时数据区、程序计数器
9 0
|
2月前
|
存储 缓存 Java
JVM 运行时内存篇
JVM 运行时内存篇
18 0