JVM内存和垃圾回收-05.虚拟机栈

1.背景

  • 由于跨平台的设计,Java指令都是根据栈来设计的(不同平台CPU架构不同,所以不能设计为基于寄存器的),这种设计方式有如下优缺点:
    • 跨平台
    • 指令集小
    • 编译器容易实现
    • 性能下降
    • 实现相同功能需要更多的指令
  • 栈是运行时的单位(栈解决程序的运行问题,即程序如何执行或者说如何处理数据),堆是存储的单位(堆解决数据存储问题,即数据放在哪,怎么放)

2.基本内容

  • 概念:每个线程在创建时都会创建一个虚拟机栈(线程私有,生命周期和线程一致),内部保存一个个的栈帧,对应一次次的Java方法调用
  • 作用:主管Java程序的运行,保存方法的局部变量(可以是8种基本数据类型或者引用类型的地址)、部分结果,并参与方法的调用和返回
  • 特点:
    • 栈是一种快速有效的分配存储方式,访问速度仅次于PC计数器
    • JVM直接对虚拟机栈的操作只有两个:
      • 方法执行时进栈
      • 方法执行结束后出栈
    • 虚拟机栈不存在垃圾回收问题,但是存在内存溢出OOM

tips:

  • JVM允许虚拟机栈的大小是动态或者固定不变,不同情况栈中可能出现不同的异常:
    • StackOverflowError:采用固定大小的虚拟机栈时,线程请求分配的栈容量超过虚拟机栈允许的最大容量(比如递归没有return)
    • OutOfMemoryError:当虚拟机栈中是动态扩展时,且在尝试扩展的时候无法申请到足够的内存或者创建新线程时没有足够的内存创建对应的虚拟机栈(机器内存不足时)
  • 通过-Xss选项设置线程的最大栈空间(栈的大小直接决定了函数调用的最大可达深度):

在这里插入图片描述


3.栈的存储单位和运行原理

3.1 存储单位
  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在
  • 在线程上正在执行的每个方法都对应一个栈帧,开始执行某方法意味着栈帧进栈,结束意味着出栈
  • 栈帧是内存区块,是一个数据集,维系着方法执行过程中的各种数据信息:
    • 局部变量表
    • 操作数栈
    • 动态链接(指向运行时常量池的方法引用)
    • 方法返回地址
    • 附加信息
3.2 运行原理
  • 一条活动线程在一个时间点上只会有一个活动的栈帧,即只有当前正在执行的方法对应的栈帧是有效的,该栈帧被称为当前栈帧
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作
  • 不同线程中包含的栈帧不允许存在相互引用
  • 正常函数返回和异常都会导致栈帧被弹出

4.栈帧的内部结构

4.1 局部变量表
  • 是一个数字数组,主要用于存储传给方法的参数和定义在方法体内的局部变量
  • 不存在数据安全问题,因为它是线程的私有数据
  • 该表所需的容量大小在编译器确定
  • 参数和局部变量越多,局部变量表越大,栈帧也就越大,函数调用的时候就会占用更多的栈空间,导致嵌套调用的次数减少
  • 最基本的存储单元是Slot(变量槽):
    • 32位之内的类型(包括引用类型)只占用一个Slot,64位的类型(long和double)占两个Slot
    • JVM会为局部变量表中每一个Slot分配一个访问索引,通过该索引可以访问局部变量值
    • 访问64位的局部变量值时,只需要使用两个索引中的第一个即可
  • 只要被局部变量表中直接或间接引用的对象都不会被回收(其中的变量是垃圾回收根节点)

tips:

  • 变量的分类:
    • 成员变量:使用前均有默认初始化赋值
      • 类变量:链接中的准备阶段会给类变量赋值;在后续的初始化阶段会赋予代码中的初值
      • 实例变量:随着类的创建会在堆中分配实例变量空间,并进行默认赋值
    • 局部变量:使用前必须要显式赋值,否则编译不通过
4.2 操作数栈
  • 在方法执行过程中,根据字节码指令需要往该栈中写入或提取数据
  • 用于保存计算过程中的临时结果,也作为计算过程中变量临时的存储空间
  • 不能通过索引的方式访问数据(虽然采用数组方式实现),只能pop和push
4.3 动态链接
  • 也被称为指向运行时常量池的方法引用,每个栈帧内部都有一个

在这里插入图片描述

在这里插入图片描述

  • 为了将这些符号引用转换为直接引用

tips:

  • 源代码被编译到字节码文件中时,所有变量和方法的引用都作为符号引用保存在class文件中的常量池中
4.4 方法返回地址
  • 存放调用该方法的pc寄存器的值(即调用该方法指令的下一条指令的地址)
  • 如果是因为异常而退出,不会给调用该方法的调用者返回任何值

5.方法的调用

  • 非虚方法:如果方法在编译期就确定了具体的调用版本,且该版本在运行时不可变,则这样的方法为非虚方法
    • 静态方法(因为静态方法不能被重写)
    • 私有方法
    • final方法
    • 实例构造器
    • 父类方法
  • 虚方法:除了非虚方法的其他方法

6.常见面试题

  • 举例栈溢出的情况:线程内有递归、大的循环时会出现溢出
  • 调整栈的大小就能保证不出现溢出吗?不能,死循环再大也会出现溢出
  • 垃圾回收会涉及虚拟机栈吗?不会,通过出栈的方法即可
  • 分配的栈内存越大越好吗?不是,会减少线程数,因为整个空间是有限的
  • 方法中定义的局部变量是否线程安全?方法中定义的变量如果作为返回值则线程不安全;如果该变量的存在周期只在方法中则是线程安全的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值