栈的定义
栈也是一块区域,用来存放数据的。栈也叫栈内存,主管Java程序的运行。
栈是私有的,是在线程创建时创建,所以它的生命期是跟随线程的生命期,线程结束栈内存也就释放。
因此对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致。
这里把方法放进栈里,说的就是方法在栈帧里面。入栈出栈说的都是栈帧。
栈的特点
- 栈空间也是不需要连续分配,只要在逻辑上相连即可。
- 栈遵循最基本的栈的数据结构特征:先进后出。
- 栈空间支付动态调整大小。-xms
- 栈空间不会出现垃圾回收。(gc作用域不在该区域)
- 栈的 5 栈的创建时机:在线程创建的时候,随之创建的。
- 栈属于线程私有区域。
- 对栈的操作,只有入栈出栈(压栈帧弹栈帧)
存储什么
栈中未来不是直接存放调用的方法,而是存储栈帧。栈帧是栈中最小单元。
8种基本类型的变量+对象的引用变量+returnAddress都是在函数的栈内存中分配。
栈帧中存储:前三个比较重要
- 存储8种基本数据类型。
- 存储对象的引用: user未来存储在栈帧中。
User user = new User();
存输方法的返回地址(returnAddress)。
-
- 任意一个万法都要存方法出去后的下一条指令地址,哪怕该方法是 void 类型也得存,因为任意的方法在底层字节码都会插入一条字节码指令
return
- 任意一个万法都要存方法出去后的下一条指令地址,哪怕该方法是 void 类型也得存,因为任意的方法在底层字节码都会插入一条字节码指令
public void a() {...b();...
}public void b() {......
}
- 存储局部变量表(Local Variable)
- 操作数栈。(Operate stack)
- 动态链接(抽象)(Dynamic link)
如图,查看字节码指令多了return
举例
当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中,
A方法又调用了 B方法,于是产生栈帧 F2 也被压入栈,
B方法又调用了 C方法,于是产生栈帧 F3 也被压入栈,
……
执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧……
遵循“先进后出”或者“后进先出”原则。
图示在一个栈中有两个栈帧:
栈帧 2是最先被调用的方法,先入栈,
然后方法 2 又调用了方法1,栈帧 1处于栈顶的位置,
栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1和栈帧 2,
线程结束,栈释放。
每执行一个方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部栈就是当前的方法,该方法执行完毕后会自动将此栈帧出栈。
常见问题栈溢出:Exception in thread "main" java.lang.StackOverflowError
通常出现在递归调用时。
JVM对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”或者“后进先出”原则。
一个线程中只能由一个正在执行的方法(当前方法),因此对应只会有一个活动的当前栈帧。
当一个方法1(main方法)被调用时就产生了一个栈帧1 并被压入到栈中,栈帧1位于栈底位置
方法1又调用了方法2,于是产生栈帧2 也被压入栈,
方法2又调用了方法3,于是产生栈帧3 也被压入栈,
…… 执行完毕后,先弹出栈帧4,再弹出栈帧3,再弹出栈帧2,再弹出栈帧1,线程结束,栈释放。
深入理解
通过在终端中输入这样的指令,可以去查看下面要讲的内容;或者idea
中的jclasslib插件
javap -verbose 类名.class
局部变量表(Local Variables)
也叫本地变量表。 作用:存储方法参数和方法体内的局部变量:8种基本类型变量、对象引用(reference)
。 可以用如下方式查看字节码中一个方法内定义的的局部变量,当程序运行时,这些局部变量会被加载到 局部变量表中。
实例方法第一个存的是this
,方便去调用方法;而类方法直接可以利用类调用,所以就不需要存储。
局部变量表槽位,一个小方格是一个槽位。
long、double 这种类型是占了两个小方格
实例方法和形参也都会提前放好
- 报错时,为什么能知道哪里错了?行号表;告诉说是哪一行报错了
操作数栈
作用: 也是一个栈在方法执行过程中根据字节码指令记录当前操作的数据,将它们入栈或出栈。用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间。
- 每一步指令都会严格的入栈出栈,想要深究可以去找
pdf
步骤。
动态连接
作用:通过符号引用动态确定某些对象。
比如可以知道当前帧执行的是哪个方法对象。程序在运行期间,通过运行时常量池
中方法的符号引用,找到方法对象。
执行一个方法的流程:(待定了,先看上面解释即可)
找方法,先得找其类 A,而类在运行时常量池
里,包括各种方法,各种属性(这里可能是哪个类在运行,其属性就在里面去找)。
拿到 A 在常量池中的符号 #1, 拿到了符号就可以去常量池拿到了类 A,编译就不会报错。
这是静态链接,只在编译的时候去找。
注意
操作数栈的深度和局部变量表的大小,是在编译阶段确定的。
栈空间溢出
Error Exception 平级,都是继承了 Throwable 类。
IDEA 中可以修改栈的大小,默认是 1MB。
这里是一直递归调用,使得一直使用方法redo
导致栈空间爆炸。
// JVM设置 -Xss128k(默认1M)
public class StatckOverflowTest1 {private static int count = 0;public static void redo(){count++;redo();}public static void main(String[] args) {try {redo();} catch (Throwable e) {e.printStackTrace();System.out.println("counter====="+count);}}
}java.lang.StackOverflowError
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
at com.atguigu.jvm.StatckOverflowTest1.redo(StatckOverflowTest1.java:13)
counter=====10213
查看文档链接