「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
每日学习,Offer滴滴
JVM内存模型
什么是栈?
Java的指令都是根据栈来设计的,栈是运行时的单位,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。
栈是线程私有的吗?
是的,栈的生命周期跟线程的生命周期一致,线程结束后栈也会释放
栈有什么特点?
- 栈是一种快速有效的分配存储方式,访问速度仪次于程序计数器
- JVM直接对Java栈的操作只有两个:每个方法执行伴随着进栈(入栈、压栈) 和 执行结束后的出栈工作
- 对于栈来说不存在垃圾回收问题GC,但存在内存溢出问题OOM
栈的结构?
每个线程都有一个虚拟机栈,栈的内部是一个个栈帧,每个栈帧由局部变量表、操作数栈、动态链接、方法返回地址构成
- 局部变量表:定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference) ,以及returnAddress类型。
- 操作数栈:主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
- 动态链接:每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking) 。多态链接实现了我们的多态写法。
- 方法返回地址:一个方法的结束,有两种方式:正常执行完成和出现未处理的异常,非正常退出。无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
栈会发生内存溢出吗?
会,栈有两种异常状况,StackOverflowError和OutOfMemoryError异常。StackOverflowError为栈的深度不足,通俗的解释就是栈帧大小的综合大于了-Xss配置的值,OutOfMemoryError则是线程创建时需要分配给它一个私有栈而内存空间不足所发生的异常。
- StackOverflowError:每当java程序代码启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。当线程调用java方法时,虚拟机压入一个新的栈帧到该线程的java栈中。只要这个方法还没有返回,它就一直存在。如果线程的方法嵌套调用层次太多(如递归调用),随着java栈中帧的逐渐增多,最终会由于该线程java栈中所有栈帧大小总和大于-Xss设置的值,而产生StackOverflowError内存溢出异常。
- OutOfMemoryError:java程序代码启动一个新线程时,没有足够的内存空间为该线程分配java栈(一个线程java栈的大小由-Xss参数确定),jvm则抛出OutOfMemoryError异常。
垃圾回收会回收栈内存吗?
不会,栈是线程私有的,生命周期随着线程的结束而结束,所以不需要GC来进行回收。
栈的大小是固定的还是动态的?
Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
- 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量, Java虚拟机将会抛出一个stackoverflowError异常。(递归操作不当容易发生stackoverflowError异常)
- 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个outofMemoryError异常。
局部变量表的容量?
局部变量表的容量以变量槽(Variable Slot,下称 Slot)为最小单位,如果访问的是 32 位数据类型的变量,索引 n 就代表了使用第 n 个 Slot,如果是 64 位数据类型的变量,则说明会同时使用 n 和 n+1 两个 Slot。
程序计数器是干什么的?
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。
那些地方用到了程序计数器?
使用java指令的地方,用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
本地方法栈是干嘛的?
本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。
- 本地方法是使用C语言实现的。
- 它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库。
- 当某个线程调用一个本地方法时,它就进入了一个全新的并且不收虚拟机限制的世界。它和虚拟机拥有同样的权限。