对线面试官:浅聊一下 Java 虚拟机栈?

简介: 对于 JVM(Java 虚拟机)来说,它有两个非常重要的区域,一个是栈(Java 虚拟机栈),另一个是堆。堆是 JVM 的存储单位,所有的对象和数组都是存储在此区域的;而栈是 JVM 的运行单位,它主管 Java 程序运行的。那么为什么它有这样的魔力?它存储的又是什么数据?接下来,我们一起来看。

对于 JVM(Java 虚拟机)来说,它有两个非常重要的区域,一个是栈(Java 虚拟机栈),另一个是堆。堆是 JVM 的存储单位,所有的对象和数组都是存储在此区域的;而栈是 JVM 的运行单位,它主管 Java 程序运行的。那么为什么它有这样的魔力?它存储的又是什么数据?接下来,我们一起来看。
1.栈定义
我们先来看栈的定义,我们这里的栈指的是 Java 虚拟机栈(Java Virtual Machine Stack)也叫做 JVM 栈,《Java虚拟机规范》对此区域的说明如下:

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:

If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

以上内容翻译成中文的含义如下:
Java 虚拟机栈是线程私有的区域,它随着线程的创建而创建。它里面保存的是局部变量表(基础数据类型和对象引用地址)和计算过程中的中间结果。Java 虚拟机的内存不需要连续,它只有两个操作:入栈和出栈。
Java 虚拟机栈要么大小固定,要么根据计算动态的扩展和收缩。程序员可以对 Java 虚拟机栈进行初始值的大小设置和最大值的设置。
Java 虚拟机栈出现的异常有两种:

当 Java 虚拟机栈大小固定时,如果程序中的栈分配超过了最大虚拟机栈就会出现 StackOverflowError 异常。

如果 Java 虚拟机栈是动态扩展的,那么当内存不足时,就会引发 OutOfMemoryError 的异常。
2.栈结构
栈是线程私有的,每个线程都有自己的栈(空间),栈中的数据是以栈帧(Stack Frame)的形式存在的,线程会为每个正在执行的方法生成一个栈帧,如下图所示:

PS:当一个新的方法被调用时,就会在栈中创建一个栈帧,当方法调用完成之后,也就意味着这个栈帧会执行出栈操作。

而栈帧中又存储了 5 个内容:

局部变量表(Local Variables);
操作(数)栈(Operand Stack);
动态链接(Dynamic Linking);
方法返回地址(Return Address);
附加信息。

如下图所示:

栈的整体存储结构如下图所示:

2.1 局部变量表
局部变量表也叫做局部变量数组或本地变量表。
局部变量表是一个数组,里面存储的内容有:

方法参数;
方法内的局部变量,也就是方法内的基本数据类型和对象引用(Reference);
方法返回类型(Return Address)。

接下来我们通过类生成的字节码来观察一下局部变量表的内容,首先,我们先来搞一个 main 方法,具体代码如下:
java复制代码public static void main(String[] args) {

int num = 0;
LocalVariablesExample lv =
        new LocalVariablesExample();

}

然后我们编译类,再使用“javap -v LocalVariablesExample.class”查看字节码生成的内容,其中包含的本地变量表内容如下:

我们通过 JClassLib 也能观察到局部变量表的信息,如下图所示为局部变量表的长度:

局部变量表的详细信息如下:

2.2 操作栈
操作栈也叫做操作数栈或表示式栈,操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
思考:为什么不把程序执行过程中的中间结果保存到局部变量表,而是保存到操作数栈中呢?
因为局部变量表是数组,而数组的长度是在其创建时就要确定,所以局部变量表在编译器就决定内容和大小了,那么在程序执行中的这些动态中间结果,是需要新的空间来保存了,而操作数栈就可以实现此功能。
2.3 动态链接
动态链接也叫做指向运行时常量池的方法引用。
这个区域的概念和作用稍微难理解一点,在每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。当一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
也就是说:当一个方法调用另一个方法时,不会再创建一个被调用的方法,而是通过常量池的方法引用来调用,而这个区域存储的就是运行时常量池的方法引用,这个区域的作用就是将运行时常量池的符号引用转换成直接引用。
2.4 方法返回地址
方法返回地址也叫做方法正常退出或异常退出的定义。
方法返回地址存放的是调用该方法的程序计数器的值。程序计数器里面保存的是该线程要执行的下一行指令的位置。
也就是说:在一个方法中调用了另一个方法,当被调用的方法执行完之后,要执行的下一行指令就是保存在此区域的。
2.5 附加信息
此区域在很多教程上会被省略,因为此区域有可能有数据,也有可能没有数据。这些附加信息是和 Java 虚拟机实现相关的一些信息。例如,对程序调试提供支持的信息。
总结
栈作为 Java 虚拟机中最核心的组成部分之一,它包含了以下 5 部分的内容:

局部变量表(Local Variables):主要存储的是方法内的基本数据类型和对象引用;
操作(数)栈(Operand Stack):主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间;
动态链接(Dynamic Linking):存放的是指向运行时常量池的方法引用;
方法返回地址(Return Address):存放的是调用该方法的程序计数器的值;
一些附加信息:存储了一些和 Java 虚拟相关的数据,比如程序的调试数据。

相关文章
|
4月前
|
Java
程序计数器和虚拟机栈
这篇文章介绍了Java虚拟机(JVM)的内存结构,特别解释了程序计数器(Program Counter Register)的作用,即用来记录下一条JVM指令的执行地址和行号。
程序计数器和虚拟机栈
|
4月前
|
存储 算法 Java
惊!Java程序员必看:JVM调优揭秘,堆溢出、栈溢出如何巧妙化解?
【8月更文挑战第29天】在Java领域,JVM是代码运行的基础,但需适当调优以发挥最佳性能。本文探讨了JVM中常见的堆溢出和栈溢出问题及其解决方法。堆溢出发生在堆空间不足时,可通过增加堆空间、优化代码及释放对象解决;栈溢出则因递归调用过深或线程过多引起,调整栈大小、优化算法和使用线程池可有效应对。通过合理配置和调优JVM,可确保Java应用稳定高效运行。
149 4
|
16天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
20天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
46 5
|
1月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
83 2
|
1月前
|
存储 算法 Java
🧠Java零基础 - Java栈(Stack)详解
【10月更文挑战第17天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
34 2
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
37 2
|
3月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
125 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
4月前
|
Java 索引
java中的栈(利用数组实现栈)
这篇文章通过Java代码示例介绍了如何使用数组实现栈操作,包括栈的初始化、入栈、出栈、判断栈满和空以及遍历栈的方法。
java中的栈(利用数组实现栈)
下一篇
DataWorks