JVM 栈和栈帧

article/2025/10/18 19:45:49

tag: jvm,stack,stack frame,栈,栈帧
原文:JVM Stacks and Stack Frames

翻译:陈同学
欢迎访问陈同学博客原文,文章可读性更佳

前情提要

对于没有深度递归的函数来说,无需担心上篇文章中的算法。当知道正在处理数据集有限时,我会使用这种简单的基本递归形式。由于你并不知道在应用程序中会处理多少数据,因此确保你的递归算法是尾递归(tail-recursive)就变得十分重要,否则你将可能遇到讨厌的 StackOverflowError.

举个例子,如果你用一个较大的list来运行上文中的sum函数,将出现 StackOverflowError,这与我们开发出稳定、出色的函数式程序相悖。

object RecursiveSum extends App {def sum(list: List[Int]): Int = list match {case Nil => 0case x :: xs => x + sum(xs)}val list = List.range(1, 10000)  // MUCH MORE DATAval x = sum(list)println(x)
}

具体到底需要多少数字才能引发 StackOverflowError,这取决于JVM的设置。Java默认的Stack大小是1024kb,这是非常小的内存。

关于尾递归,下篇文章我会继续讲解,本文我想讨论下JVM Stack 和 Stack frames。如果你不熟悉这些概念,本文有助于你理解,同时有益于debug stack trace

什么是 Stack ?

为了理解递归算法中可能发生的 “stack overflow”问题,你需知晓写的递归算法到底发生了什么。

首先需要知晓的是:所有计算机语言都有 “” 这个概念,也称之为 “调用栈“。

Java/JVM中栈的官方定义

Oracle关于栈和栈帧提供了如下描述:

每个JVM线程拥有一个私有的 Java虚拟机栈,创建线程的同时栈也被创建。一个JVM栈由许多帧组成,称之为"栈帧"。JVM中的栈和C等常见语言中的栈比较类似,都用于保存局部变量和部分计算结果,同时也参与方法调用和返回。

因此,你可以想象一下,一个栈拥有许多栈帧,如下图:

the stack

如Oracle官方说明,每个线程拥有自己的私有栈,因此在多线程应用中将有多个栈,每个栈有自己的栈帧。

Java中的栈

为了更好的解释栈,下面所有的引用都都来自一个免费的在线电子书,由 Bill Venners 撰写的 《Inside the Java Virtual Machine》

当一个新的线程创建时,JVM会为这个线程创建一个新的Stack。一个Java Stack在一个个独立的栈帧中存储了线程的状态。JVM只会在Java Stack中做两个操作:push 和 pop.
一个线程当前正在执行的方法称之为线程的 当前方法,当前方法对应的栈帧称为 当前帧,当前方法所属的类称为 当前类,当前类的常量池称为 当前常量池。 在执行一个方法时,JVM会保存当前类和当前常量池的轨迹。当JVM执行 需要操作栈帧中数据的指令时,JVM会在当前栈帧进行处理。
当一个线程执行一个Java方法时,JVM将创建一个新的栈帧并且把它push到栈顶。此时新的栈帧就变成了当前栈帧,方法执行时,使用栈帧来存储参数、局部变量、中间指令以及其他数据。

什么是栈帧?

Bill Venners 书中所言:

栈帧由三部分组成:局部变量表、操作数栈以及帧数据。

书中还有:

每个方法涉及的局部变量表和操作数栈的大小取决于每个具体的方法,但是大小在编译后便已确定,而且已经包含在class文件中。

重要的是:栈帧的大小因局部变量表和操作数栈而异。书中对于size的描述如下:

JVM执行一个方法时,它会检查class中的数据,以便确定一个方法执行时在局部变量表和操作数栈中所需存储的word size。然后,JVM会为当前方法创建一个size相对应的栈帧,然后把它push到栈顶。

Word size、操作数栈和常量池

简述下 word size、operand stack(操作数栈)、constant pool(常量池)这三个短语的定义:

  • word size:它是一个度量单位,在不同类型的JVM中,word size的大小不一定会相同。但是,它至少会有32位以保证可以存储long或double类型。

  • operand stack:操作数栈在 oracle.com 中被定义。捎带提一嘴,其中的定义涉及到了机器代码,例如:它展示了使用iadd指令进行两个integer的加法操作。 欢迎你深入学习这些细节,但是我们这里只想让你简单知道:操作数栈只是栈帧中的一块内存区域。

  • 常量池:Java运行时常量时在 oracle.com 中定义. 写道:一个运行时常量池 … 包含了许多种常量,编译时的常见数值字面量、运行时需要处理的方法和字段引用等. 运行时常量池和一些编程语言中的符号表有点类似,只不过它比符号表存储的数据范围更广。

小结

关于栈和栈帧,我们做个小结:

  • 每个JVM线程有一个私有栈,栈在线程创建的同时被创建。
  • 栈由许多帧组成,也叫 “栈帧”
  • 每次方法调用都会创建一个栈帧

换句话说,当一个Java/Scala/JVM方法被执行时:

  • 当方法被执行时,一个新的栈帧被创建并用来给这个方法存储数据
  • 栈帧大小各不相同,取决于方法的参数、局部变量和算法
  • 当一个方法被执行时,程序只能访问当前栈帧中的数据,你能看到的只有栈顶的帧

最后一个关于栈和递归的资源

最后我想分享下 Sedgewick 和 Wayne 《Algorithms》 书中的一个讨论:

上面有两行关于递归的非常重要的信息:

  • 当方法调用return时,数据将从stack中pop出来,这样程序才能恢复到调用者处继续执行。
  • 递归算法有时会产生非常深的调用从而耗尽栈的空间

分析

上述讨论都是为了希望你看到递归算法可能产生的潜在问题:

  • 当一个方法递归调用自己时,新的方法所产生的数据(也可以理解为新的栈帧)将会被push到栈顶
  • 方法每次调用自己时,会拷贝一份当前方法的数据并push到栈中。因此,递归的每层调用都需要创建一个新的栈帧
  • 这样的结果是,栈中越来越多的内存将随着递归调用而被消耗,如果递归调用自己一百万次,那么将会产生一百万个栈帧

http://chatgpt.dhexx.cn/article/lvfiEq1G.shtml

相关文章

C语言的函数栈帧

⭐️前面的话⭐️ 📒博客主页:未见花闻的博客主页 🎉欢迎关注🔎点赞👍收藏⭐️留言📝 📌本文由未见花闻原创,CSDN首发! ✉️坚持和努力一定能换来诗与远方! &…

【C语言】函数栈帧的创建和销毁(1)

前言: ❓ 在我们前期学习C语言时,你是否曾产生过很多困惑? 💭 比如:局部变量是怎么创建出来的?因为局部变量的值是随机值,我们建议将它初始化,那么为什么局部变量的值是随机值&am…

函数栈帧(详解版)

文章目录 前言1.浅谈C语言内存1.1 内存分配1.2 栈1.3 寄存器 2. 为main()函数开辟栈帧3.变量的初始化及函数调用4.ADD函数4.1 ADD函数的创建4.2 ADD函数使用与销毁 5.总结 前言 前期学习的时候,我们可能有很多困感? 比如: ●局部变量是怎么创建的?I为什么局部变量…

也谈栈和栈帧

 一个码农要是没遇见过coredump,那他就是神仙了。core file(coredump的转储文件)中保存的最重要内容之一,就是函数的call trace。还原这部分内容 (栈回溯) ,并与原代码对应上,尽快找出程序崩溃的位置和…

(栈帧和函数调用一)栈帧,函数调用与栈的关系

(栈帧和函数调用一)栈帧,函数调用与栈的关系 一,栈帧的介绍二,函数调用与栈的关系三,汇编演示四,总结 在计算机科学中,栈是一个特殊的容器,用户可以将数据压入栈中&#…

理解栈帧和栈的运行原理

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法 (Method) 和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中&…

函数栈帧的形成与释放

✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!! 📃个人主页:rivencode的个人主页 🔥系列专栏:玩转C语言 💬推荐一…

【函数栈帧的创建和销毁】(超详细图解)

想必大家在学完C语言函数章节之后,是否有这样的困惑: 局部变量是怎么创建的 ? 为什么局部变量的值是随机值 ? 函数是怎么传参的?传参的顺序又是什么样的 ? 形参和实参是什么关系 ? 函数调用…

C语言函数栈帧详解

系列文章目录 前言 最近正在学习栈帧方面的知识,由于本人对汇编不太熟悉,对其中频繁出现的ESP寄存器和EBP寄存器一直没搞清楚,在查找资料后,在此进行整理,方便以后温故知新。 一、预备知识 要清楚理解栈帧的概念&…

详解栈帧结构

https://www.1024do.com/?p367 栈帧结构 含义:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录,是编译器用来实现过程函数调用的一种数据结构。 从逻辑上讲,栈帧就是…

函数栈帧详解

目录 一.什么是函数栈帧 1.寄存器: 2.函数栈帧 3.栈帧的作用和维护 4.栈帧结构 二.函数栈帧的创建 1.汇编代码 2.main函数函数栈帧的创建 1.汇编语言讲解: 2.栈帧创建: 3.详细步骤 3.ADD函数栈帧的创建 栈帧创建: 3.函…

栈帧

首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。下图为典型的存取器安排,观察栈在其中的位置 入栈操…

什么是函数栈帧

函数栈帧的创建与销毁 一、函数栈帧的创建1.寄存器2.函数栈帧3.函数中调用函数 二、函数栈帧的销毁总结 一、函数栈帧的创建 1.寄存器 一般来说,计算机中的寄存器有六种 分别是:eax, ebx, ecx,edx,ebp,esp 而ebp,esp这两个寄存器中存放的是地址&#…

栈帧 stack frame

栈帧 stack frame 每一次函数调用都会维护一个栈帧(stack frame),栈帧主要用于传递参数、保存返回地址、保存局部变量等。先直接上一个《深入理解计算机系统》上的原图。 其中,%rsp 指向栈顶位置,%rbp 指向栈底位置。…

C/函数栈帧

🌱博客主页:大寄一场. 🌱系列专栏:C语言学习笔记 😘博客制作不易欢迎各位👍点赞⭐收藏➕关注 ​ 目录 前言 寄存器 1. 寄存器的种类与功能 C语言汇编指令介绍 函数栈帧的创建与销毁过程 1.函数栈帧的…

浅谈函数栈帧(Stack Frame)

💙作者:阿润菜菜 📖专栏:C 本文目录 什么是栈帧 在调试中观察 总结 什么是栈帧 那我们先来看看什么是栈: 栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构,它按照后进先出的原则存储…

栈和栈帧

栈 堆栈(stack)又称为栈或堆叠,是计算机科学里最重要且最基础的数据结构之一,它按照FILO(First In Last Out,后进先出)的原则存储数据。 栈的相关概念: 栈顶和栈底:允许元素插入与删除的一端…

函数栈帧的创建和销毁(图解)

目录 基础知识介绍1. 寄存器的种类与功能2. 常用汇编指令3. 内存模型 演示函数栈帧的创建销毁过程1. 为main()函数开辟栈帧2. 在main()函数中创建变量3. 调用Add()函数前的准备4. 为Add()函数开辟栈帧5. 在Add()函数中创建变量并运算6. Add()栈帧的销毁7. 返回main()函数栈帧 总…

运行时栈帧结构是怎样的?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 栈帧(Stack Frame)是J…

函数栈帧(详细图解)

目录 一、栈 二、常用寄存器及简单汇编指令 三、理解栈帧 3.1 main函数栈帧创建 3.1.1 main函数栈帧创建动态演示 3.2 局部变量创建 3.2.1 局部变量创建动态演示 3.3 函数传参与调用 3.3.1 函数传参 3.3.2 函数传参动态演示 3.3.3 函数调用 3.3.4 函数返回 四、END…