JVM–基础–01–内存区域–栈
1、结构图
2、虚拟机栈(java栈)
2.1、特征
- 线程私有
- 后进先出(LIFO)栈
- 线程请求的栈深度大于虚拟机允许的深度,出现StackOverflowError 异常
- 虚拟机在扩展栈时无法申请到足够的内存空间,出现OutOfMemoryError 异常
- 和本地方法栈区别: 虚拟机栈描述的是Java方法执行的内存模型,存储栈帧,支撑 Java 方法的调用、执行和退出
2.2、栈帧
2.2.1、概念和特征
- 在java栈中保存的主要内容为栈帧。
- 一个完整的栈帧包含:局部变量表、操作数栈、动态连接信息、方法正常完成和异常完成信息
- 每一次函数调用,都会有一个对应的栈帧被压入java栈,每一个函数调用结束,都会有一个栈帧被弹出java栈。如上图:栈帧和函数调用。函数1对应栈帧1,函数2对应栈帧2,依次类推。函数1中调用函数2,函数2中调用函数3,函数3调用函数4.当函数1被调用时,栈帧1入栈,当函数2调用时,栈帧2入栈,当函数3被调用时,栈帧3入栈,当函数4被调用时,栈帧4入栈。
- 当前正在执行的函数所对应的帧就是当前帧(位于栈顶),它保存着当前函数的局部变量、中间计算结果等数据。
2.2.2、在概念模型上,典型的栈帧结构如图所示
2.3、局部变量表概念和特征
- 由若干个 Slot(局部变量空间) 组成,长度由编译期决定。
- 单个Slot可以存储一个类型为 boolean、byte、char、short、float、reference(对象引用) 和 returnAddress(类型) 的数据。两个Slot可以存储一个类型为long或double(64位长度)的数据。
- 用于保存方法的参数以及局部变量,局部变量表中的变量只在当前函数调用中有效,当函数调用结束,随着函数栈帧的弹出销毁,局部变量表也会随之销毁。
- 对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。
- 局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
- 通过索引访问
2.4、方法返回地址概念
- 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
- 由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。也就解释了栈是线程私有的。
2.5、操作数栈的概念和特征
- 是一个后进先出栈,由若干个 Entry 组成,长度由编译期决定。
- 单个 Entry 即可以存储一个 Java 虚拟机中定义的任意数据类型的值,包括 long和 double 类型,但是存储 long 和 double 类型的 Entry 深度为2,其他类型的深度为1。
- 和局部变量区一样,操作数栈也是被组织成一个以字节为单位的数组。
- 通过标准的栈操作(压栈和出栈)来访问的。
- 程序中的所有计算过程都是在借助于操作数栈来完成的。
2.6、StackOverflowError(可通过-Xss来调教栈深度)
由于每次函数调用都会产生对应的栈帧,从而占用一定的栈空间,因此,如果栈空间不足,那么函数调用自然无法继续进行下去。
当请求的栈深度大于最大可用栈深度时,系统会抛出StackOverflowError栈溢出错误。
package com.fei.zhou.day1;
public class StackOverflowErrorTest {
private static int count = 0;
public static void main(String[] args) {
try {
test();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
private static void test() {
// 描述:使用递归,由于递归没有出口,这段代码可能会抛出栈溢出错误
System.out.println("最大的调用深度:" + (count++));
test();
}
}
结果:
最大的调用深度:12554
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:561)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
at java.io.PrintStream.write(PrintStream.java:526)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:19)
at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)
at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)
at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)
2.7、OutOfMemoryError
我这就不测试了,只要调小内存大小,再来一个无限增加堆内存的方法,就会抛出该异常
3、本地方法栈(native method stack)
3.1、本地方法栈的特征
- 线程私有
- 后进先出(LIFO)栈
- 可能出现 OutOfMemoryError 异常和 StackOverflowError 异常
- 有一些虚拟机(如Sun HotSpot)将 Java 虚拟机栈和本地方法栈合二为一
- 和虚拟机栈区别:作用是支撑虚拟机使用的 Native 方法的调用、执行和退出