Java 栈
Java 虚拟机栈:Java Virtual Machine Stacks,每个线程运行时所需要的内存
-
每个方法被执行时,都会在虚拟机栈中创建一个栈帧 stack frame(一个方法一个栈帧)
-
Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定不变的
-
虚拟机栈是每个线程私有的,每个线程只能有一个活动栈帧,对应方法调用到执行完成的整个过程
-
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存,每个栈帧中存储着:
-
局部变量表:存储方法里的 Java 基本数据类型以及对象的引用
-
动态链接:也叫指向运行时常量池的方法引用
-
方法返回地址:方法正常退出或者异常退出的定义
-
操作数栈或表达式栈和其他一些附加信息
-
设置栈内存大小:-Xss size
-Xss 1024k
-
在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M
虚拟机栈特点:
-
栈内存不需要进行GC,方法开始执行的时候会进栈,方法调用后自动弹栈,相当于清空了数据
-
栈内存分配越大,可用的线程数越少(内存越大,每个线程拥有的内存越大)
-
方法内的局部变量是否线程安全:
-
如果方法内局部变量没有逃离方法的作用访问(即在方法的作用范围内,是局部变量的时候),它是线程安全的(逃逸分析)
-
如果是局部变量引用了对象,并逃离方法的作用范围,或者返回了对象,就需要考虑线程安全
-
异常:
-
栈帧过多导致栈内存溢出 (超过了栈的容量),会抛出 OutOfMemoryError 异常
-
当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常
局部变量
局部变量表也被称之为局部变量数组或本地变量表,本质上定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量
-
表是建立在线程的栈上,是线程私有的数据,因此不存在数据安全问题
-
表的容量大小是在编译期确定的,保存在方法的 Code 属性的 maximum local variables 数据项中
-
表中的变量只在当前方法调用中有效,方法结束栈帧销毁,局部变量表也会随之销毁
-
表中的变量也是重要的垃圾回收根节点,只要被表中数据直接或间接引用的对象都不会被回收
局部变量表最基本的存储单元是 slot(变量槽):
-
参数值的存放总是在局部变量数组的 index0 开始,到数组长度 -1 的索引结束,JVM 为每一个 slot 都分配一个访问索引,通过索引即可访问到槽中的数据
-
存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress 类型的变量
-
32 位以内的类型只占一个 slot(包括 returnAddress 类型),64 位的类型(long 和 double)占两个 slot
-
局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么之后申明的新的局部变量就可能会复用过期局部变量的槽位,从而达到节省资源的目的
操作数栈
栈:可以使用数组或者链表来实现
操作数栈:在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)或出栈(pop)
-
保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间,是执行引擎的一个工作区
-
Java 虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈
-
如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中
栈顶缓存技术 ToS(Top-of-Stack Cashing):将栈顶元素全部缓存在 CPU 的寄存器中,以此降低对内存的读/写次数,提升执行的效率
基于栈式架构的虚拟机使用的零地址指令更加紧凑,完成一项操作需要使用很多入栈和出栈指令,所以需要更多的指令分派(instruction dispatch)次数和内存读/写次数,由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度,所以需要栈顶缓存技术