
2.4 函数如何调用:出入Java栈
Java栈是一块线程私有的内存空间。如果说Java堆和程序数据密切相关,那么Java栈就是和线程执行密切相关的。线程执行的基本行为是函数调用,每次函数调用的数据都是通过Java栈传递的。
Java栈与数据结构中的栈有着类似的含义,它是一块先进后出的数据结构,只支持出栈和入栈两种操作。在Java栈中保存的主要内容为栈帧。每一次函数调用,都会有一个对应的栈帧被压入Java栈,每一次函数调用结束,都会有一个栈帧被弹出Java栈。如图2.5所示,函数1对应栈帧1,函数2对应栈帧2,依此类推。函数1中调用函数2,函数2中调用函数3,函数3中调用函数4。当函数1被调用时,栈帧1入栈;当函数2被调用时,栈帧2入栈;当函数3被调用时,栈帧3入栈;当函数4被调用时,栈帧4入栈。当前正在执行的函数所对应的帧就是当前的帧(位于栈顶),它保存着当前函数的局部变量、中间运算结果等数据。
当函数返回时,栈帧从Java栈中被弹出。Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。
在一个栈帧中,至少要包含局部变量表、操作数栈和帧数据区几部分。

图2.5 帧栈和函数调用
提示:由于每次函数调用都会生成对应的栈帧,从而占用一定的栈空间。因此,如果栈空间不足,那么函数调用自然无法继续进行。当请求的栈深度大于最大可用栈深度时,系统就会抛出StackOverflowError栈溢出错误。
Java虚拟机提供了参数-Xss来指定线程的最大栈空间,这个参数也直接决定了函数调用的最大深度。
【示例2-3】下面的代码是一个递归调用,由于递归没有出口,这段代码可能会出现栈溢出错误,在抛出错误后,程序打印了最大的调用深度。


使用参数-Xss128K执行以上代码,部分结果如下:

可以看到,在进行大约2500次调用后,发生了栈溢出错误,通过增大-Xss的值,可以获得更深的调用层次,尝试使用参数-Xss256K执行上述代码,可能产生如下输出,很明显,调用层次明显增加。

注意:函数嵌套调用的层次在很大程度上由栈的大小决定,栈越大,函数可以支持的嵌套调用次数就越多。