ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JVM-虚拟机栈

2021-07-26 11:34:36  阅读:121  来源: 互联网

标签:调用 java 虚拟机 局部变量 引用 JVM 方法 栈帧


1 虚拟机栈概述

背景:由于跨平台的设计,java指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器。

栈是运行时单位,而堆是存储的单位。即:栈解决程序运行的问题,即程序如何执行,或者说如何处理数据,堆解决是数据存储的问题,即数据怎么放,放在哪。

栈中可能出现的异常:java栈的大小可以是动态的也可以是固定不变的。

如果采用固定大小的虚拟机栈,那么如果线程请求超过栈容量,会出现StackOverflowError异常。

如果java虚拟机可以动态扩展,当无法申请扩展到足够的内存的时候,会抛出OOM(OutOfMemoryError)异常。

栈的运行原理:

JVM直接堆Java栈的操作只有两个,就是入栈和出栈,遵循先进后出的原则,所以没有GC(垃圾回收)。

java栈中存储的数据是栈帧,一个活动线程的一个时间点上只有一个活动的栈帧,如果在方法中调用了其他方法,那么新的栈帧会被创建出来,放在栈的顶端,成为新的栈帧。

不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。

如果当前方法调用了其他方法,方法返回时,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新称为当前栈帧。

java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常,不管哪一种方式,都会导致栈帧被弹出。

栈的内部结构:

1)局部变量表

2)操作数栈

3)动态链接

4)方法返回地址

5)一些附加信息

2 局部变量表

定义为一个数字数字,主要用于存储方法参数和定义在方法体内的局部变量。

因为时定义在线程私有的虚拟机栈上,所以很多时候不存在线程安全问题(当局部变量作为函数参数传递一点点牵强)、作为函数返回值时会出现线程安全问题)。

局部变量表所需的容量大小是在编译期间确定下来的,并保存在方法的Code 属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小。

局部变量表中的变量只在当前方法调用中有效,在方法执行时,虚拟机通过局部变量表完成参数值到参数变量列表的传递过程。在方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁

Slot:

局部变量表的最基本的存储单元就是slot(槽)

在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型)-存储的时候都是以int类型存储,64位的类型(long和double)占用两个slot。

jvm会为局部变量中每一个slot都分配一个访问索引,通过整访问索引可以成功范文到局部变量表中指定的局部变量

如果当前帧时由构造方法或者实例方法创建的(非静态方法),那么该对象的引用this将会存放在index为0的slot处。

 

slot的 重复利用:

栈帧中的局部变量表中的槽位时可以重复利用的,如果一个局部变量过了其作用域(如类里面的代码块),后面申请的局部变量就可能重复利用过期的局部变量的槽位。

 

局部变量与静态变量的对比:

静态变量的初始化有两个阶段,第一次是在准备阶段,会对其分配存储空间和默认的初始化值,第二次是在初始化阶段,赋予程序员在代码中定义的初始值。

局部变量不存在系统初始化的过程,所以一旦定义,必须认为进行初始化,不然无法使用。

3 操作数栈

操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据。主要用于保存计算结果过程的中间结果,同时作为计算过程中的变量临时的存储空间。

操作数栈中可以存储任意类似的java数据,32bit的类型占用一个栈单位深度,64位的类型占用两个栈单位深度。

如果调用的方法带有返回值,其返回值会被压入当前栈帧的操作数据栈中,并更新PC寄存器中下一条需要执行的字节码指令。

java虚拟机的解释引擎是基于栈的执行引擎,其中的栈就是操作数栈

4 动态链接

每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属的方法的引用。包含这个引用的目的就是支持当前方法的代码能够实现动态链接。比如invokedynamic指令。java属于静态语言,是判断变量本身类型信息,java8中lambda的出现,invokedynamic指令的生成,使得java有了直接生成的方式,有了一个动态语言的特性。

在java源文件被编译到字节码文件中时,所有变量和方法引用都作为符号引用保存在class文件的常量池里,动态链接就是为了将这些符号引用转换位调用方法的直接引用。

5 方法调用

在jvm中,将符号引用转换位调用方法的直接引用与方法的绑定机制相关。

静态链接:当一个字节码文件被装入jvm内部的时候,如果被调用的目标方法在编译期可知,且运行期间保持不变,这种情况下将调用方法的符号引用转换位直接引用的过程称为静态链接。

动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期间将调用方法的符号引用转化位直接引用。

 

早期绑定:

早期绑定就是指被调用方法如果在编译期可知,且运行期不变,就可以将这个方法所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟时哪一个,因此可以使用静态链接。

晚期绑定:如果被调用方法在编译期无法被确定下来,只能在程序运行期间根据实际的类型绑定相关的方法。

 

非虚方法:静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法,其他都是虚方法(没有多态的方法)。因为java每个函数都具有虚函数的性质。

 

方法重写的本质:

1)找到操作数栈顶的第一个元素所执行的对象实际类型,记作c。

2)如果类型c中找到与常量中符合函数简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结果;如果不通过,则返回java.lang.IllegalAccessSError异常。

3)否则,则按照继承关系从下往上依次对C的各个父类进行第二步.

4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethidError异常。

 

虚方法表:

为了提高性能,jvm采用在类的方法区建立一个虚方法表来实现。使用索引表来代替查找。表中存放着各个方法的实际入口。

虚方法的创建?

虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成后,jvm会把类的方法表也初始化完毕。

6 方法的返回地址

一个方法的结束,有两种方式:正常执行完成和出现未处理的异常。

无论通过哪种方式退出,在方法退出后都返回该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令执行地址。而通过异常退出的,返回地址是要通过异常表来确定。区别在于异常完成出口退出不会给他的上层调用者产生任何的返回值。

 

 

标签:调用,java,虚拟机,局部变量,引用,JVM,方法,栈帧
来源: https://www.cnblogs.com/slwyj/p/15060665.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有