文章福利:阿里巴巴编码规范-各版本下载
每一个方法的运行都伴随着栈帧的入栈到出栈过程,那么栈帧中有什么呢?
既然每个方法都被映射成一个个栈帧,方法中有什么则栈帧中或多或少都应该也有什么吧,思路是没问题的。
方法中有什么呢?
如下是一个很普通的方法:
public String test(String a, Integer b){
int c = 0;
int d = c + b;
return a;
}
该方法中包含如下信息:
-
方法参数a 和 b;
-
局部变量c 和 d;
-
返回值 a;
-
其他隐藏信息;
栈帧中有什么呢?
对比上述方法中包含的信息,那么栈帧中应当有存储方法里的参数、局部变量、返回值等信息的数据结构及其他细节信息,答案是肯定的。
明白了栈帧的设计是为了运行时方法信息的存储,下面就来看看栈帧中具体包含那些信息
一个栈帧中应当包含以下信息:
局部变量表、操作数栈、动态链接、返回值信息和其他信息。
本篇暂不打算说栈帧中的的局部变量表等信息,本篇和小伙伴们大致学习了解下对栈帧的初步印象。
关于栈帧的其他重要信息
当一个线程中的方法调用链很长的时候,其对应的栈帧数量也就很多,每个栈帧都占有一定大小的空间,在代码编译期每个方法对应的栈帧大小就已经确定,而虚拟机栈的大小是不确定的,一般一个线程栈默认大小为1MB,可以修改该值。
这里一定要注意,编译期只是确定了栈帧中需要多大的变量表,而程序运行后需要多大的虚拟机栈内存是不知道的,这样就会出现一种问题:
不正确的递归调用会导致栈内存溢出(stackoverflow),因为不正确的使用递归,运行期间递归方法一直在循环调用,每个占用的栈帧空间一直没有被释放,调用次数到达虚拟机栈内存的最大内存后就会出现栈溢出的情况。
递归导致栈溢出演示:
public class JVMTes {
private int stackDepth = 0;
public void test(){
stackDepth++;
test();
}
public static void main(String[] args) {
JVMTes jvmTes = new JVMTes();
try {
jvmTes.test();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("调用次数:" + jvmTes.stackDepth);
}
}
}
运行结果:
调用次数:995
Exception in thread "main" java.lang.StackOverflowError
at main.java.jvm.JVMTes.test(JVMTes.java:8)
at main.java.jvm.JVMTes.test(JVMTes.java:9)
at main.java.jvm.JVMTes.test(JVMTes.java:9)
at main.java.jvm.JVMTes.test(JVMTes.java:9)
at main.java.jvm.JVMTes.test(JVMTes.java:9)
Process finished with exit code 1
栈的OOM情况
若虚拟机栈有一个动态扩展内存机制,线程申请的栈内存空间大于预设值,先去动态扩展内存,若扩展后还是不够线程申请的栈内存大小,此时就会内存溢出
然而,栈的OOM情况有些虚拟机是没有实现的,比如我们常见的HotSpot
附:Java虚拟机规范关于栈的异常:
-
若线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
-
若虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError异常
--- END ---
关注公众号 Java岛 ,每天进步亿点点,和小编一起成长Java呀!