什么是虚拟机栈

发布时间:2021-10-11 11:26:20 作者:iii
来源:亿速云 阅读:113

这篇文章主要介绍“什么是虚拟机栈”,在日常操作中,相信很多人在什么是虚拟机栈问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是虚拟机栈”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

虚拟机栈

一 虚拟机栈概述

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

优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

有不少Java开发人员一提到Java内存结构,就会非常粗粒度地将JVM中的内存区理解为仅有Java堆(heap)和Java栈(stack)?为什么?

首先栈是运行时的单位,而堆是存储的单位

什么是虚拟机栈

Java虚拟机栈是什么

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

是线程私有的

什么是虚拟机栈

生命周期

生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了

作用

主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。

局部变量,它是相比于成员变量来说的(或属性)

基本数据类型变量 VS 引用类型变量(类、数组、接口)

栈的特点

什么是虚拟机栈

开发中遇到哪些异常?

栈中可能出现的异常

 演示栈中的异常:StackOverflowError

public class StackErrorTest {
    private static int count = 1;
    public static void main(String[] args) {
        System.out.println(count++);
        main(args);
    }
}

当栈深度达到9803的时候,就出现栈内存空间不足

设置栈内存大小

我们可以使用参数 -Xss 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度

-Xss1m
-Xss1024k

二 栈的存储单位

1 栈中存储什么?

复习: 

2 栈运行原理

什么是虚拟机栈

下面写一个简单的代码

/**
 * 栈帧
 *
 */
public class StackFrameTest {
    public static void main(String[] args) {
        method01();
    }

    private static int method01() {
        System.out.println("方法1的开始");
        int i = method02();
        System.out.println("方法1的结束");
        return i;
    }

    private static int method02() {
        System.out.println("方法2的开始");
        int i = method03();;
        System.out.println("方法2的结束");
        return i;
    }
    private static int method03() {
        System.out.println("方法3的开始");
        int i = 30;
        System.out.println("方法3的结束");
        return i;
    }
}

输出结果为

方法1的开始
方法2的开始
方法3的开始
方法3的结束
方法2的结束
方法1的结束

满足栈先进后出的概念,通过Idea的 DEBUG,能够看到栈信息

什么是虚拟机栈

栈运行原理

3 栈帧的内部结构

每个栈帧中存储着:

什么是虚拟机栈

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

什么是虚拟机栈

三 局部变量表

1 关于Slot的理解

什么是虚拟机栈

Slot的重复利用

栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。

什么是虚拟机栈

2  举例:静态变量与局部变量的对比

变量的分类

什么是虚拟机栈

补充说明:

四 操作数栈

概念

栈:可以使用数组或链表来实现

操作数栈:Operand Stack

什么是虚拟机栈

代码举例

什么是虚拟机栈

操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。.

这个时候数组是有长度的,因为数组一旦创建,那么就是不可变的

每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为maxstack的值。

栈中的任何一个元素都是可以任意的Java数据类型

操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问

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

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。|

另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。

五 代码追踪

我们给定代码

public void testAddOperation() {
    byte i = 15;
    int j = 8;
    int k = i + j;
}

使用javap 命令反编译class文件: javap -v 类名.class

什么是虚拟机栈

byte、short、char、boolean 内部都是使用int型来进行保存的

从上面的代码我们可以知道,我们都是通过bipush对操作数 15 和 8进行入栈操作

同时使用的是 iadd方法进行相加操作,i -> 代表的就是 int,也就是int类型的加法操作

执行流程如下所示:

首先执行第一条语句,PC寄存器指向的是0,也就是指令地址为0,然后使用bipush让操作数15入栈。

什么是虚拟机栈

执行完后,让PC + 1,指向下一行代码,下一行代码就是将操作数栈的元素存储到局部变量表1的位置,我们可以看到局部变量表的已经增加了一个元素

什么是虚拟机栈

为什么局部变量表不是从0开始的呢?

其实局部变量表也是从0开始的,但是因为0号位置存储的是this指针,所以说就直接省略了~

然后PC+1,指向的是下一行。让操作数8也入栈,同时执行store操作,存入局部变量表中

什么是虚拟机栈

什么是虚拟机栈

然后从局部变量表中,依次将数据放在操作数栈中

什么是虚拟机栈

什么是虚拟机栈

然后将操作数栈中的两个元素执行相加操作,并存储在局部变量表3的位置

什么是虚拟机栈

什么是虚拟机栈

最后PC寄存器的位置指向10,也就是return方法,则直接退出方法

i++和++i的区别

六 栈顶缓存技术

栈顶缓存技术:Top Of Stack Cashing

前面提过,基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派(instruction dispatch)次数和内存读/写次数。

由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存(Tos,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

寄存器:指令更少,执行速度快

七 动态链接(或指向运行时常量池的方法引用)

动态链接:Dynamic Linking

什么是虚拟机栈

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

什么是虚拟机栈

为什么需要运行时常量池?

因为在不同的方法,都可能调用常量或者方法,所以只需要存储一份即可,节省了空间

常量池的作用:就是为了提供一些符号和常量,便于指令的识别

八 方法调用:解析与分配

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

链接

绑定机制

对应的方法的绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。

早晚期绑定的发展历史

随着高级语言的横空出世,类似于Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性,既然这一类的编程语言具备多态特悄,那么自然也就具备早期绑定和晚期绑定两种绑定方式。

Java中任何一个普通的方法其实都具备虚函数的特征,它们相当于C++语言中的虚函数(C++中则需要使用关键字virtual来显式定义)。如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。

虚方法和非虚方法

子类对象的多态性的使用前提

虚拟机中提供了以下几条方法调用指令:

前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。

关于invokednamic指令

动态类型语言和静态类型语言

Java:String info = "mogu blog"; (Java是静态类型语言的,会先编译就进行类型检查)

JS:var name = "shkstart"; var name = 10; (运行时才进行检查)

方法重写的本质

Java 语言中方法重写的本质:
IllegalAccessError介绍

方法的调用:虚方法表

什么是虚拟机栈

如上图所示:如果类中重写了方法,那么调用的时候,就会直接在虚方法表中查找,否则将会直接连接到Object的方法中。

九 方法返回地址

存放调用该方法的pc寄存器的值。

当一个方法开始执行后,只有两种方式可以退出这个方法:

什么是虚拟机栈

本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。

正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

十 一些附加信息

栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如:对程序调试提供支持的信息。

十一 栈的相关面试题

/**
 * 面试题
 * 方法中定义局部变量是否线程安全?具体情况具体分析
 * 何为线程安全?
 *    如果只有一个线程才可以操作此数据,则必是线程安全的
 *    如果有多个线程操作,则此数据是共享数据,如果不考虑共享机制,则为线程不安全
 */
public class StringBuilderTest {

    // s1的声明方式是线程安全的
    public static void method01() {
        // 线程内部创建的,属于局部变量
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        s1.append("b");
    }

    // 这个也是线程不安全的,因为有返回值,有可能被其它的程序所调用
    public static StringBuilder method04() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("a");
        stringBuilder.append("b");
        return stringBuilder;
    }

    // stringBuilder 是线程不安全的,操作的是共享数据
    public static void method02(StringBuilder stringBuilder) {
        stringBuilder.append("a");
        stringBuilder.append("b");
    }


    /**
     * 同时并发的执行,会出现线程不安全的问题
     */
    public static void method03() {
        StringBuilder stringBuilder = new StringBuilder();
        new Thread(() -> {
            stringBuilder.append("a");
            stringBuilder.append("b");
        }, "t1").start();

        method02(stringBuilder);
    }

    // StringBuilder是线程安全的,但是String也可能线程不安全的
    public static String method05() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("a");
        stringBuilder.append("b");
        return stringBuilder.toString();
    }
}

总结一句话就是:如果对象是在内部产生,并在内部消亡,没有返回到外部,那么它就是线程安全的,反之则是线程不安全的。

运行时数据区,是否存在Error和GC?

运行时数据区是否存在Error是否存在GC
程序计数器
虚拟机栈
本地方法栈
方法区是(OOM)

到此,关于“什么是虚拟机栈”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. 什么是栈,队列
  2. 探究Java虚拟机栈

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:Windows server2008下基于Apache2.4如何配置php7.2运行环境

下一篇:Windows server2008如何配置php支持SQL server

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》