Java开发中,每当我们在程序中使用new生成一个对象,对象的引用存放在栈里,而对象是存放在堆里的。可以看出栈在Java核心的重要位置。今天我们就继续深入Java核心这个系列,为您介绍Java中的栈、局部变量及其之间的关系。
Java中的栈
每当启用一个线程时,JVM就为他分配一个Java栈,栈是以帧为单位保存当前线程的运行状态。某个线程正在执行的方法称为当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。当线程执行一个方法时,它会跟踪当前常量池。
每当线程调用一个Java方法时,JVM就会在该线程对应的栈中压入一个帧,这个帧自然就成了当前帧。当执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等。 也就是启动一个线程就会分配它一个栈的空间,而线程调用一个方法,那么就会针对这个方法在栈里开辟一个小的专门给这个方法用的空间,叫做帧,帧里又划分为三个区:局部变量区、操作数栈、帧数据区,这个要结合前面一篇文章(尽可能使用堆栈变量(转))一起看,就知道编译后各个区间的使用情况。
Java栈上的所有数据都是私有的。任何线程都不能访问另一个线程的栈数据。所以我们不用考虑多线程情况下栈数据访问同步的情况。
像方法区和堆一样,Java栈和帧在内存中也不必是连续的,帧可以分布在连续的栈里,也可以分布在堆里。
Java栈的组成元素——栈帧
栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,他们是按字长计算的。但调用一个方法时,它从类型信息中得到此方法局部变量区和操作数栈大小,并据此分配栈内存,然后压入Java栈。
局部变量区 局部变量区被组织为以一个字长为单位、从0开始计数的数组,类型为short、byte和char的值在存入数组前要被转换成int值,而long和double在数组中占据连续的两项,在访问局部变量中的long或double时,只需取出连续两项的第一项的索引值即可,如某个long值在局部变量区中占据的索引时3、4项,取值时,指令只需取索引为3的long值即可。
下面就看个例子,好让大家对局部变量区有更深刻的认识。这个图来自《深入JVM》:
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的局部变量区第一项是个reference(引用),它指定的就是对象本身的引用,也就是我们常用的this,但是在runClassMethod方法中,没这个引用,那是因为runClassMethod是个静态方法。
操作数栈和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。可把操作数栈理解为存储计算时,临时数据的存储区域。下面我们通过一段简短的程序片段外加一幅图片来了解下操作数栈的作用。
int a = 100;
int b = 98;
int c = a+b;
从图中可以得出:操作数栈其实就是个临时数据存储区域,它是通过入栈和出栈来进行操作的。
帧数据区除了局部变量区和操作数栈外,Java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在Java栈帧的帧数据区中。
当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。
除了处理常量池解析外,帧里的数据还要处理Java方法的正常结束和异常终止。如果是通过return正常结束,则当前栈帧从Java栈中弹出,恢复发起调用的方法的栈。如果方法又返回值,JVM会把返回值压入到发起调用方法的操作数栈。
为了处理Java方法中的异常情况,帧数据区还必须保存一个对此方法异常引用表的引用。当异常抛出时,JVM给catch块中的代码。如果没发现,方法立即终止,然后JVM用帧区数据的信息恢复发起调用的方法的帧。然后再发起调用方法的上下文重新抛出同样的异常。
栈的整个结构
在前面就描述过:栈是由栈帧组成,每当线程调用一个Java方法时,JVM就会在该线程对应的栈中压入一个帧,而帧是由局部变量区、操作数栈和帧数据区组成。那在一个代码块中,栈到底是什么形式呢?下面是我从《深入JVM》中摘抄的一个例子,大家可以看看:
代码片段:
执行过程中的三个快照:
上面所给的图,只想说明两件事情,我们也可用此来理解Java中的栈:
1、只有在调用一个方法时,才为当前栈分配一个帧,然后将该帧压入栈。
2、帧中存储了对应方法的局部数据,方法执行完,对应的帧则从栈中弹出,并把返回结果存储在调用方法的帧的操作数栈中。
分享到:
相关推荐
在Java程序执行过程中,JVM会根据方法的调用关系创建和销毁栈桢。 二、栈桢的结构 栈桢的结构可以分为以下几个部分: * 局部变量数组(Local Variables):存储方法的局部变量的值。 * 操作数栈(Operand Stack)...
深入JVM概要 JVM详解 本文将详细介绍Java虚拟机(JVM)的内部机理和实现...JVM中的类型的生命周期、方法区、常量池、类加载器、垃圾收集器、栈和局部变量等都是JVM的重要组件,它们共同构成了Java虚拟机的基本架构。
在《JVM指令手册》中,主要涵盖了栈和局部变量的操作,这是理解JVM工作原理的关键部分。 1. 栈和局部变量操作: JVM使用栈来存储临时计算结果,局部变量表则是存储方法参数和局部变量的地方。以下是一些主要的指令...
同时,局部变量表中的变量也是垃圾回收的根节点,只要被局部变量表引用的对象都不会被回收,因此理解栈帧结构有助于理解内存管理和垃圾收集机制。 总结来说,Java虚拟机栈是Java程序执行的基础,它管理着方法调用的...
首先,JVM指令手册中提到的操作栈和局部变量的指令分为两大类:常量压栈指令和局部变量加载指令。常量压栈指令用于将各种类型的常量值(null, int, long, float, double, char, byte, short)压入操作数栈。例如,a...
main 方法中,我们可以看到虚拟机栈的作用,包括局部变量表、操作数栈、动态连接和方法出口四部分的作用。 JVM 虚拟机栈是 Java 程序执行的核心部分,负责管理 Java 程序的执行。理解虚拟机栈的作用是非常重要的,...
- `aload`: 从局部变量中装载引用类型值(reference)。 - 各种`iload`、`lload`、`fload`、`dload`、`aload`的具体实现,如`iload_0`,表示从局部变量0中装载相应类型值。 #### 将栈中的值存入局部变量的指令 - `...
3. **代码优化**:JVM可能会对`final`局部变量进行优化,如逃逸分析,将它们存储在栈上而不是堆上,提高运行效率。 4. **阅读性与可维护性**:使用`final`关键字可以增加代码的可读性,因为它明确地告诉其他开发者...
5. 操作数栈:JVM使用一个先进后出(FILO)的数据结构来存储局部变量和中间计算结果,这个结构就是操作数栈。所有指令都是基于操作数栈来执行的,例如,将值推送到栈上(push),从栈上弹出值(pop)等。 6. 参数与...
每个线程都会有一个程序计数器和栈来跟踪方法调用,栈中存放了栈帧,其中包含了局部变量区和操作数栈。 在Java平台中,JDK(Java Development Kit)是开发Java程序的软件开发包,而JRE(Java Runtime Environment)...
JVM 栈是线程私有的,每个线程创建的同时都会创建 JVM 栈,JVM 栈中存放的为当前线程中局部基本类型的变量(java 中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果...
它主要用于存储方法调用过程中的局部变量、操作数栈等数据。每个线程在其生命周期内都会有一个对应的堆栈结构,其中包含了一个或多个帧(Frame)。每个帧对应一个方法调用,包含了该方法的局部变量表、操作数栈以及...
栈的主要任务是为每个方法的执行提供一个栈帧(Stack Frame),栈帧存储了方法执行过程中的局部变量、操作数栈、动态连接和方法返回地址等关键信息。 栈帧是虚拟机栈的元素,它包含了以下几个重要组成部分: 1. ...
1. **存储位置**:成员变量位于堆内存中的对象中,而局部变量位于栈内存中。 2. **生命周期**:成员变量的生命周期与对象相同,局部变量的生命周期与方法或代码块相同。 3. **默认值**:成员变量有默认值,而局部...
- **数据类型转换**:`byte`、`short`、`char`和`boolean`类型在局部变量区中会被转换为`int`类型存储。这是因为JVM不直接支持这些较小的数据类型,而是将其统一转换为`int`类型进行处理,以简化指令集设计。 - **...
栈帧是执行方法的基础结构,包含局部变量表、操作数栈、动态链接、方法出口等信息。对于本地方法栈,它会登记本地方法并在执行时加载到本地方法库。由于本地方法不受Java虚拟机的限制,它们可以直接访问操作系统资源...
栈内存主要用于存储程序运行过程中的局部变量、方法参数、常量池引用等。每次函数调用都会创建一个新的栈帧,包含该函数的所有局部变量。栈内存的分配和释放都非常快,因为它遵循“先进后出”(LIFO)原则。当函数...
本手册将详细介绍JVM指令集中与栈和局部变量操作相关的指令,这有助于深入理解Java程序在JVM中的运行机制。 首先,栈和局部变量操作指令可以分为两大类:一类是将常量压入操作数栈的指令,另一类是从栈中向局部变量...