JVM 栈和栈帧

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 => 0
        case x :: xs => x + sum(xs)
    }

    val list = List.range(1, 10000)  // MUCH MORE DATA
    val 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官方说明,每个线程拥有自己的私有栈,因此在多线程应用中将有多个栈,每个栈有自己的栈帧。

image

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 书中所言:

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

书中还有:

每个方法涉及的局部变量表和操作数栈的大小取决于每个具体的方法,但是大小在编译后便已确定,而且已经包含在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》 书中的一个讨论:

image

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

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

分析

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

  • 当一个方法递归调用自己时,新的方法所产生的数据(也可以理解为新的栈帧)将会被push到栈顶
  • 方法每次调用自己时,会拷贝一份当前方法的数据并push到栈中。因此,递归的每层调用都需要创建一个新的栈帧
  • 这样的结果是,栈中越来越多的内存将随着递归调用而被消耗,如果递归调用自己一百万次,那么将会产生一百万个栈帧
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容