文章目录
栈概述
-
由于跨平台的设计,java指令都是根据栈(指令集后进先出)来设计的。不同平台的cpu架构不同,所以不能设计基于寄存器。
-
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多功能。
栈与堆的区别
- 栈是运行时的单位,堆是存储的单位
栈解决的是程序的运行问题,即程序怎样执行,怎样处理数据。堆解决的是数据存储的问题。
栈的基本内容
-
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次次的java方法调用
-
生命周期和线程一致
作用:主管java程序的运行,保存了方法的局部变量,并参与方法的调用和返回
栈的特点
-
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
-
jvm直接对栈的操作有两个
每个方法执行,伴随着进栈(入栈,压栈)。
执行结束后出栈。
-
对于栈来说不存在垃圾回收问题
不存在gc问题,但是存在内存溢出问题。
-
设置栈的大小
-Xss
虚拟机栈可能出现的异常
Java中虚拟机规范允许java的栈的大小是动态的或者是固定不变的
-
如果采用的是固定大小的java虚拟机栈,那每一个线程的java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量,java虚拟机就会抛出一个StackOVerflowError 异常
-
如果java虚拟机可以动态扩展,在尝试扩展的时候无法申请到足够的内存,或者在创建新线程没有足够的内存去创建对应的虚拟机栈,将会抛出 OutOfMemoryError
栈存储的数据
-
每个线程都有自己的栈,栈中的数据都是以**栈帧(Stack Frame)**格式存在
-
在这个线程上,正在执行的方法都有各自对应的栈帧
-
栈帧是一个内存区域,是一个数据集 维系着方法执行过程中的数据
栈运行的原理
-
jvm对栈的操作只有两个 当方法执行时进行 压栈,当方法结束时执行 出栈 ,遵循“先进后出”、“后进先出”原则
-
在一条活动线程中,一个时间点,只会有一个活动栈帧,即只有当前正在执行的方法栈帧是有效的,这个栈帧被称为 “当前栈帧”,与当前栈帧对应的方法就是 “当前方法”,定义这个方法的类就是**“当前类”**
-
执行引擎运行的所有字节码指令值针对当前栈帧进行操作
-
如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧
-
java方法中有两种返回函数方式,一种是正常的函数返回,使用return指令,另外一种是抛出异常,无论怎样,都会导致栈帧被弹出
抛出异常后 还是一return返回的
/**
* @program: jvmDemo
* @description: 抛出异常后,方法还是以return结束的
* @author: wfg
* @create: 2021-06-11 12:49
*/
public class Test6 {
static int count;
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("hello");
}
private static void method1() {
int i = 10 / 0;
}
}
结果
栈帧里面存储的数据
-
局部变量表
-
操作数栈
-
动态链接
-
方法返回地址
-
一些附加信息
局部变量表
-
定义为一个数字数组,主要用于存储方法参数和地你一方法体内的局部变量,这些数据类型包括(基本数据类型,对象引用,返回值信息)
-
局部变量表示建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
-
局部变量表所需的容量大小是在编译其就确定下来的
-
局部变量表的变量只在当前方法的调用中有效,当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁
jclasslib 中对栈区的分析
字节码指令 对应的java代码显示
局部变量表中slot分析
-
局部变量表,最基本的存储单元是slot(变量槽)
-
在局部变量表中,32位以内的类型只占用一个slot(包括返回值),64位类型(long 和double)占用两个slot
-
byte,short,char 在存储前被转化成int,boolean 被转换程int,0表示false,非0表示true
-
栈帧中的**局部变量表中的槽位是可以重用的,**在其作用域之后申明的新的局部变量很可能复用过期的局部变量,从而达到节省资源的目的
局部变量表补充
-
在方法执行时,虚拟机使用局部变量表完成方法的传递
-
局部变量表中的变量也是重要的垃圾回收根节点,只要局部变量表中直接或间接引用的对象都不会被回收。
操作数栈
-
每个独立的栈帧中除了包含局部变量表以为,还包含一个后进先出的操作数栈
-
操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据
-
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
-
操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过表准的入栈和出栈操作来完成一次数据访问
-
我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈
栈顶缓存技术
由于操作数是存储在内存中的,因此频繁执行内存读、写操作会影响执行速度,因此jvm设计者提出栈顶缓存技术将栈顶元素全部缓存在物理cpu的寄存器中,以此降低对内存读写次数,提升执行引擎的执行效率
动态链接
-
每一个栈帧内部都包含一个指向运行时常量池该栈帧所属方法的引用
-
java文件被编译成class文件时,变量和方法的引用都作为符号引用被保存在class文件,动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
方法的调用
链接
- 静态链接
当字节码文件被加载到jvm时,如果方法调用的目标在编译期可知,且运行期间保持不变。这种情况下将调用方法的符号引用装换成直接引用称之为 静态链接
- 动态链接
被调用的方法在编译期无法被确定下来,只能在运行期将调用方法的符号引用转换为直接引用,称之为动态链接
方法的绑定
绑定是一个字段,方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次
- 早期绑定
早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变,即可将这个方法与所属的类型进行绑定
- 晚期绑定
被调用的方法在编译期无法被确定下来,只能在程序运行期根据实际的类型绑定相关的方法,则称为晚期绑定
虚方法与非虚方法
非虚方法:
-
如果方法在编译期就确定了其他的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法。
-
静态方法,私有方法,final方法,实例构造器,父类方法都是非虚方法。
-
其他方法称为虚方法
常用的方法指令
-
invokestatic: 调用静态方法,解析阶段确定为方法
-
invokespecial: 调用方法,私有及父类方法
-
invokevirtural:调用所有虚方法
-
invokeinterface: 调用接口方法
-
invokedynamic:动态解析出需要调用的方法
invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余(final修饰的除外)称为虚方法。
invokeddynamic解析
- 动态语言与静态语言
静态类型语言是判断自身变量的类型信息,动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有。
静态语言
int a =10;
string name ="1";
动态语言
var name ="111";
var name=10;
因此可见java是静态类型语言,而invockedynamic指令是java 为了实现动态类型语言支持而做的一种改进,而实现方式时java8中引入的lambda表达式。
java方法重写的本质
-
找到操作数栈顶的第一个元素所执行对象的实际类型 记作c。
-
如果在类型中c中找到与常量中描述符合简单名称都相符的方法,则进行访问权限校验,通过则直接放回这个方法的直接引用,如果不通过,则返回java.lang.illegealAccessError 异常。
-
否则,按照继承关系从下往上依次对 c 的各个父类进行第二步的搜索和验证
-
如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError;
方法返回地址
-
存放调用该方法的pc寄存器的值。
-
方法的结束,有两种方式
正常执行完成。
出现为处理的异常,非正常退出
方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。异常退出,返回地址是通过异常表来确定的
本地方法栈
本地方法
简单的讲,一个native method 就是一个java调用非java代码的接口
本地方法栈
-
java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用
-
本地方法栈,也是线程私有的
-
本地方法是使用c语言实现的