虚拟机栈(线程私有)

虚拟机栈(线程私有)

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

一、基本概念

1、概述

栈 vs 堆:

  • :解决的是程序运行的问题,即程序如何执行,或者说如何处理数据。
  • :解决的是数据存储的问题,即数据怎么放,放哪里

虚拟机栈的生命周期:

  • 和线程的生命周期一致

虚拟机栈的作用:

  • 描述 Java方法 的内存模型,保存方法的局部变量、部分结果,并参与方法的调用和返回。

栈的特点:

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
  • 对于栈来说不存在垃圾回收问题(但是栈存在SOF和OOM的问题)
  • 栈的大小直接决定了方法调用的最大可达深度

2、设置栈内存大小

# 栈
-Xss128k					设置每个线程的栈大小(包括初始大小以及栈的动态扩展行为)
-XX:ThreadStackSize=128k 	设置每个线程的初始栈大小(只影响每个线程的初始栈大小)		

3、栈相关的异常

在不同的 Java 虚拟机实现中,对于栈的大小可以有不同的处理方式,可以是 动态扩展的 或 固定不变的:

  • 固定不变的 Java 虚拟机栈

    在虚拟机启动时就确定了。在这种情况下,虚拟机会为每个线程分配固定大小的栈空间,并且不会随着程序的运行而动态调整。这样的实现可能会更简单、更高效,但是如果栈空间不足,就有可能导致栈溢出异常(StackOverflowError)。

  • 动态扩展的 Java 虚拟机栈

    虚拟机栈的大小可以根据程序的需求动态增长。这种情况下,虚拟机会根据需要动态地调整栈的大小,以满足程序运行时的需求。这样可以在一定程度上避免因为栈空间不足而导致的栈溢出问题。不过,动态扩展也可能带来一些性能开销。

对于 HotSpot VM,一般来说,虚拟机栈的大小是固定的,这个固定的大小由 -Xss 参数来设置。

1)StackOverflowError

如果 线程请求分配的栈容量 超过 虚拟机栈允许的最大容量,就会抛出StackOverflowError异常。(常见于递归)

package stack;

/**
 * 栈超出最大深度:StackOverflowError
 * - 默认情况下:count:31857
 * - VM options 设置栈的大小:-Xss512k  count:4924
 */
public class StackSOF {
   
   

    private int stackLength = 1;

    // 递归
    public void recursion() {
   
   
        stackLength++;
        recursion();
    }

    public static void main(String[] args) {
   
   
        StackSOF stackSOF = new StackSOF();
        try {
   
   
            stackSOF.recursion();
        } catch (Throwable e) {
   
   
            System.out.println("当前栈深度:" + stackSOF.stackLength);
            e.printStackTrace();
        }
    }
}

2)OutOfMemoryError

  • 在尝试扩展的时候无法申请到足够的内存,就会抛出OutOfMemoryError异常。

  • 在创建新线程的时候没有足够的内存去创建对应的虚拟机栈,也会抛出OutOfMemoryError异常。

以下代码示例谨慎使用,可能会引起电脑卡死

package stack;

/**
 * 栈内存溢出: OOM
 * VM options 设置栈的大小:-Xss2m
 **/
public class StackOOM {
   
   

    public static void main(String[] args) {
   
   
        StackOOM stackOOM = new StackOOM();
        stackOOM.stackLeakByThread();
    }

    // 不断创建线程 -> 不断创建Java虚拟机栈 -> 不断申请内存 -> 内存溢出
    public void stackLeakByThread() {
   
   
        while (true) {
   
   
            Thread t = new Thread(new Runnable() {
   
   
                public void run() {
   
   
                    dontStop();
                }
            });
            t.start();
        }
    }

    private void dontStop() {
   
   
        while (true) {
   
   
        }
    }

}
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
	at java.lang.Thread.start0(Native Method)
	at java.lang.Thread.start(Thread.java:719)
	at stack.StackOOM.stackLeakByThread(StackOOM.java:22)
	at stack.StackOOM.main(StackOOM.java:11)

二、栈的运行原理

每个线程在创建时,都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用

1、栈的存储单位 - 栈帧

每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。

在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)。

栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

2、当前栈帧

一个线程,一个时间点上,只会有一个活动的栈帧,这个栈帧被称为 当前栈帧(Current Frame)

  • 当前栈帧相对应的方法就是当前方法(Current Method)
  • 定义这个方法的类就是当前类(Current Class)

执行引擎 运行的所有字节码指令 只针对 当前栈帧 进行操作。

3、压栈 & 出栈

JVM 直接对 Java 栈的操作只有两个,就是对 栈帧的压栈和出栈,遵循「先进后出」「后进先出」的 原则。

  • 压栈:
    • 每当方法被调用时,对应的栈帧会被创建出来,压入栈顶。(栈顶的栈帧 即 当前栈帧
    • 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,压入栈顶,成为 新的当前栈帧
  • 出栈:
    • 正常的方法返回(使用 return 指令) 和 抛出异常,都会导致栈帧被弹出。
    • 方法返回时,当前栈帧将当前方法的执行结果传给前一个栈帧,接着,当前栈帧出栈,前一个栈帧成为当前栈帧。

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

在这里插入图片描述

4、方法的执行过程

  • 方法调用前:创建栈帧
  • 方法执行时:栈帧入栈
  • 方法执行后:栈帧出栈

三、栈帧的内部结构

栈帧是用来存储数据和部分过程结果的数据结构,每个栈帧中存储着:

  • 局部变量表(Local Variables)
  • 操作数栈(Operand Stack)(或表达式栈)
  • 动态链接(DynamicLinking)(或指向运行时常量池的方法引用)
  • 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
  • 一些附加信息

有些地方将 动态链接、方法返回地址、附加信息 统称为 帧数据区。

每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要是由 局部变量表 和 操作数栈 决定的

在这里插入图片描述

1、局部变量表(Local Variables)

局部变量表用于存储方法中的局部变量,包括方法参数以及在方法中定义的局部变量

  • 基本数据类型(byte、short、int、long、float、double、char、boolean)
  • 对象引用(对象实例存在堆中)
  • returnAddress类型(指向了一条字节码地址)

1)特点

  • 局部变量表存储在栈帧中,是线程私有的,因此 不存在数据安全问题
  • 局部变量表的大小是在编译时确定的,在方法运行期间不会改变
  • 局部变量表中的变量只在当前方法的调用中有效。方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

2)Slot 变量槽

局部变量表是一个数组结构,每个元素都存储一个局部变量的值。最基本的存储单元是 Slot(变量槽)

  • 32 位以内的类型只占用一个 Slot(包括 returnAddress 类型),64 位的类型占用两个 Slot。

    • 引用数据类型(32位),占用一个 Slot

    • int类型(32位),占用一个 Slot

      byte、short、char 在存储前被转换为 int,boolean 也被转换为 int,0 表示 false,非 0 表示 true。

    • long类型 和 double类型(64位),占用两个 Slot

  • JVM 会为局部变量表中的每一个 Slot 都分配一个访问索引,通过这个索引即可成功访问到指定的局部变量值。

  • 当方法被调用时,方法参数方法中定义的局部变量按照顺序 被复制到局部变量表中的每一个 Slot 上。

如果是 构造方法实例方法,Index=0 的 Slot 存放的是对象引用this,其余的参数 按

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scj1022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值