当前位置:首页 » 《关注互联网》 » 正文

2021-08-12虚拟机栈_刺客凌凌漆的博客

20 人参与  2021年08月15日 11:23  分类 : 《关注互联网》  评论

点击全文阅读


1.虚拟机栈

1.1概述

作用:主管java程序的执行,存储的是栈帧(稍后介绍),栈帧主要存储局部变量,部分结果,以及方法的调用和返回
特点

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
  • jvm对虚拟机栈的操作只有两个,伴随着方法的执行入栈、结束出栈
  • 不存在垃圾回收问题

栈中可能会出现的问题
根据java虚拟机规范,虚拟机栈的大小是动态或者固定不变的

  1. 如果采用固定的栈,每个线程的虚拟机栈可以在线程创建的时候独立完成,如果线程请求分配的栈容量超过了虚拟机栈允许的最大容量,会抛出StackOverflowError(堆栈溢出)
  2. 如果采用动态的栈,在尝试扩展的时候无法申请足够的内存,或者在创建新线程时没有足够的内存创建对应的虚拟机栈,就会抛出OutOfMemoryError(内存溢出)

1.2栈帧的存储单位

  • 每个线程都有自己的栈,栈中的数据都以**栈帧(Stack Frame)**的形式存在
  • 在当前线程上正在执行的方法都有各自对应的栈帧
  • 栈帧是一块内存区域、数据集,维护着方法执行过程中的各种数据信息

1.3栈运行原理

  1. jvm对栈的操作只有两个,压栈\出栈,遵循”FILO“原则
  2. 一个正在运行的线程,只会有一个运行的栈帧,就是当前方法执行的当前栈帧,也叫栈顶栈帧,与当前栈帧对应的就是当前方法,与当前方法对应的就是当前类
  3. 执行引擎运行的所以字节码文件只对当前栈帧进行操作
  4. 不同线程的栈帧是不允许相互引用的,所以不可能在一个栈帧中引用另一个线程的栈帧
  5. 如果方法中调用了其他方法,对应的栈帧也会创建出来,成为新的当前栈帧,放在栈顶
  6. 如果方法中调用了其他方法,方法返回的时候,当前栈帧会返回一个执行结果给前一个栈帧,随后虚拟机会丢弃当前栈帧,让前一个栈帧成为当前栈帧
  7. java中有两种方法返回方式:1,return。2,抛异常。两种方式都会导致栈帧被弹出
  8. idea在执行debug的时候,可以在Frames窗口各个栈帧的压栈、出栈

在这里插入图片描述

1.4栈帧的内部结构

  1. 局部变量表
  2. 操作数栈
  3. 动态链接
  4. 方法返回地址
  5. 其他附加信息
    在这里插入图片描述

1.4.1局部变量表

主要用于存储方法参数和方法体中的局部变量
可以存储的类型有:

  1. java基本数据类型
  2. 对象引用(可能是指向对象地址的引用指针,或是指向代表对象的句柄或相关地址)
  3. returnAddress类型(指向方法返回的字节码指令,已被异常表取代)

由于局部变量表是存在线程的栈上的,是线程私有的数据,所以不存在数据安全问题
局部变量表的容量大小在编译期就确定下来的,并保存在方法的Code属性的maxmum local variables数据项中,运行期是不会改变表的大小的
方法嵌套调用的次数由栈的大小决定,一般情况下,栈越大,方法嵌套调用越多

1.4.1.1槽 slot

  • 局部变量表的基本单位是slot(变量槽)
  • 在局部变量表中,32位的数据类型占用一个slot,64位则占用两个slot

注:
byte,short,char,boolean(0表示false,1表示true)在存储之前会转为int
long和double占两个slot

  • jvm会为局部变量表中的每个slot分配一个索引,通过索引访问对应的slot
  • 如果需要访问一个64位的局部变量时,只需要前一个索引即可。如(long类型的变量占用了a1,a2两个slot,此时需要访问a1即可访问到该变量)
  • 如果当前栈帧是由构造方法或实例方法创建的,那么this将会存放在index为0的slot中(此处引出一个问题:静态方法为什么不能引用this?就是因为this变量不存在当前方法的局部变量表中)
  • 在栈帧中,与性能调优关系最为密切的就是局部变量表。在方法执行时,虚拟机会使用局部变量表来完成方法的传递
  • 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收
  • slot是可以重用的
    在这里插入图片描述
    上图:局部变更表中有四个变量
    注:至于这里为什么this没有在第一个位置,是因为在执行构造方法之前会先执行非静态代码块
    在这里插入图片描述
    上图:但是局部变量最大槽数只有3,因为代码块中变量b使用完就销毁了,但是b的slot还存在,所以就被变量c重用了
    拓展:图片中的方法为构造方法,在字节码文件中构造方法叫init,静态代码块叫clinit,而非静态代码块就没别的名字,因为它里面的局部变量会被存在init方法对于栈帧中

1.4.2操作数栈(LIFO)

  • 在方法执行过程中,根据字节码指令,对操作数栈写入(入栈push)和提取(出栈pop)数据。
  • 某些字节码指令将值写入操作数栈,某些指令将值取出栈,再进行相关操作,比如加减乘除。结果值也会放入操作数栈。

概述:

  1. 操作数栈,主要用于保存计算过程的中间结果,同时作为变量计算后的临时的存储空间
  2. 操作数栈是jvm执行引擎的一个工作区,当一个方法执行时,新的栈帧出现,此时这个操作数栈是空的
  3. 操作数栈都会有一个明确的大小来用于存储数值,所需要的最大容量再编译器就定义好了,在方法的Code属性的max_stack
  4. 栈中的元素可以是java中的任意数据类型,与slot一样(32位占一个单位,64占两个单位)
  5. 如果被调用的方法有返回值,那返回值会被压到当前栈帧的操作数栈中,并通知程序计数器下一条需要执行的字节码指令
  6. jvm的解释引擎是基于栈的执行引擎,栈指的是操作数栈

1.4.3动态链接(指向常量池的方法引用)

静态绑定:当一个字节码文件被加载到虚拟机时,如果其调用的方法在编译器可知,并且在运行期保持不变
动态绑定:相反,如果在编译器不能确定下来,只能在运行期将符号引用转为直接引用

  • 每个栈帧中都包含一个指向常量池中该栈帧所属方法的引用,目的就是为了实现动态链接
  • 在java文件被编译成字节码文件时,所以变量和方法引用都会作为符号引用保存在Class文件的常量池中
  • 比如:描述一个方法调用其他方法时,就是通过常量池中指向方法的符号引用来表示,动态链接的作用就是将这些符号引用转换为调用该方法的直接引用
    在这里插入图片描述

1.4.3.1jvm时如何调用方法的

  1. 方法调用阶段的唯一任务就是找到需要调用发方法,暂时还不涉及内部运行过程
  2. 方法在Class文件中存储的是符号引用,而不是在运行时的直接引用,也就是需要在类加载阶段,甚至是运行期才能确定方法的直接引用

1.4.3.1.1在jvm中方法的符号引用转化为直接引用跟绑定机制有关
绑定机制有早期绑定(静态绑定)、晚期绑定(动态绑定),绑定是一个字,方法或类的符号引用转为直接引用的过程,只会发生一次

1.4.3.1.2虚方法和非虚方法

  1. 在编译期可以确定下来,在运行期不变,称为非虚方法。比如,静态方法、final方法、私有方法、父类方法,构造方法都是非虚方法
  2. 其他方法叫虚方法

1.4.3.1.3虚方法表

  • 为了提高性能,jvm在类的方法区建立了一个虚方法表,用于存放虚方法。因为在面向对象编程中,会频繁设计动态分配,每次动态分配都在类的方法元数据中查找合适的目标。
  • 每个类都会有一个虚方法表,存放着各个方法的实际入口
  • 虚方法表会在类加载的连接(验证、准备、解析)阶段开始初始化,类的变量初始值准备完成之后,虚方法表也初始化完毕

1.4.4f方法返回地址

  • 用来存放调用该方法的程序计数器值

  • 方法的两种返回方式,不管是那种,在方法推出后都会返回到调用该方法的位置

     1.正常返回时:调用者的程序计数器的值作为返回地址,就是调用该方法的指令的下一条指令地址	
     2.异常返回时:通过异常表来确定的,栈帧中一般不会保存这部分信息
    
  • 实际上,方法的退出就是当前栈帧出战的过程,此时需要回到上一个方法,让方法执行下去

  • 正常返回与异常返回的区别在于:异常返回不会给上层调用者任何的返回信息

1.4.5附带信息

栈帧中还可以携带一些jvm相关信息。比如,对程序调试支持的信息,但是这些信息取决于虚拟机的实现


点击全文阅读


本文链接:http://zhangshiyu.com/post/25407.html

方法  引用  局部  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1