JAVA虚拟机栈

栈是运行时的单位,堆是存储的单位。

在这里插入图片描述

JAVA虚拟机栈的概念
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次次的方法调用,是线程私有的(生命周
期和线程一致)。主管JAVA程序的运行
虚拟机栈的特点
1.访问速度快,仅次于程序计数器。
2.JVM直接对栈的操作有两个,及入栈(方法调用)和出栈(方法执行完毕)。
3.对于栈来说不存在垃圾回收。
栈可能出现的异常
1.固定大小的栈(通过-Xss设定)如果线程请求分配的栈空间大于设定的值,会抛出StackOverflowError。
2.动态扩展的栈,在尝试扩展时无法申请到足够的内存,或者在创建新线程时没有足够的内存去创建对应的虚拟机栈,则会报OutOfMemoryError。
栈的存储单位:栈帧
每个线程都有自己的栈,在这个线程上执行的每个方法都各自对应一个栈帧。而且执行引擎运行的所有字节码指令只针对栈顶的那个栈帧进行操作。如果当前方法调用了别的方法,对应的新的栈帧会被创建,并且放到栈顶。栈为线程私有,所以两个线程之间的栈帧无法互相引用
栈帧当中包含:
1.局部变量表(本地变量表)
.局部变量表定义为一个数组,大小在编译时期确定,主要用于存储方法参数和定义在方法体内的局部变量(基本数据类型,对象引用)。
在这里插入图片描述
如图,序号0代表this(非静态方法通过对象调用,所以有一个this代表当前对象),序号1为方法参数a,序号2位局部变量b,所以这个方法的局部变量表槽数为3。
局部变量表的最基本存储单元为槽(Slot)32位的,long和double占两个槽(访问时访问第一个槽的索引),其余占一个槽(byte、short、char、boolean都会被转换为int),JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可访问到表中局部变量的值。
在这里插入图片描述
在栈帧中,与性能调优关系最为密切的部分就是局部变量表,在方法执行时,虚拟机使用局部变量表完成方法的传递。
局部变量表中的变量也是垃圾回收的重要根节点,只要被局部变量表中直接引用或间接引用的对象都不会被回收。
2.操作数栈(操作“数”的栈)
操作数栈主要用于保存计算过程的中间结果,同时作为计算中变量的临时存储空间。最大深度也在编译期确定,和局部变量表一样long和double也占两个栈单位深度。操作数栈不能通过索引访问,只能入栈和出栈来完成数据防问。
如果被调用的方法有返回值,他的返回值会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。

在这里插入图片描述
栈顶缓存:将栈顶元素全部缓存到物理cpu的寄存器中,降低堆内存的读写次数,提升执行引擎的效率。
3.动态链接
在运行时,每个栈帧(方法)中都保存着指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了方法调用过程中的动态链接。在运行时符号引用转换为直接引用(非静态方法,只有在运行时才能确定真正执行的方法,比如只有运行时才能知道真正是哪个子类的方法该被执行),这种转换称之为动态链接。
静态链接:在类加载的链接的解析阶段,对于静态方法会直接由符号引用转换为直接引用。
在这里插入图片描述

方法的调用
静态链接和动态链接
在这里插入图片描述
早期绑定和晚期绑定
在这里插入图片描述
比如一个方法的参数传入了一个父类,这个就是晚期绑定,因为不运行这个方法就不知道传入的是子类还是父类(多态)。非静态方法也是这种,只有在运行时才确定调用它的是父类还是子类。
非虚方法和虚方法
在这里插入图片描述
虚拟机提供的方法调用指令
在这里插入图片描述

    public void show() {
        //invokestatic静态方法
        showStatic("atguigu.com");
        //invokestatic父类静态方法
        super.showStatic("good!");
        //invokespecial私有方法
        showPrivate("hello!");
        //invokespecial父类普通方法 这个也为非虚方法!!因为编译期就确定
        super.showCommon();
        //invokevirtual
        showFinal();//因为此方法声明有final,不能被子类重写,所以也认为此方法是非虚方法。
        //虚方法如下:
        //invokevirtual 父类的方法,没有用super 所以也可能是子类重写过,所以为虚
        showCommon();
        //子类的普通方法为虚方法
        info();
        MethodInterface in = null;
        //invokeinterface //接口的方法为也为虚方法
        in.methodA();
    }

虚方法表
在面向对象的编程中,会频繁的用到动态分配,为了提高性能,每个类都有一个虚方法表(在类的方法去中),存放各个方法的实际入口。虚方法表会在类加载的链接阶段被创建并初始化,类的变量初始值准备完成之后,JVM会把该类的虚方法表也初始化完毕。(不用每次调用一个方法都去寻找,本类,父类,父类的父类这样)
在这里插入图片描述
dog类自定义了sayhello,重写了tostring 所以这两个存到自己的虚方法表。
在这里插入图片描述

CockerSpaniel继承dog类,实现了Friendly接口,重写了sayHello和sayGoodbye方法,所以这两个方法存到自己的虚方法表,而tostring存到dog的虚方法表中。
在这里插入图片描述
cat类实现了Friendly接口,重写了sayHello和sayGoodbye方法,重写了object类的finalize方法和tostring方法,自定义了eat方法,所有这5个方法都存到自己的虚函数表中。
动态类型语言和静态类型语言
在这里插入图片描述
java是静态类型语言,比如 定义 int a =10;是在编译器就要检查a的类型的。int a = “1”;编译要报错
4.方法返回地址
存放调用该方法的PC寄存器的值
意思就是方法A调用方法B,方法B中存放一条方法A的PC寄存器的值,等待方法B执行完毕之后,B中的方法返回地址可以保证方法A继续准确执行。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.一些附加信息
6.面试题
在这里插入图片描述
1.固定栈大小,如果溢出,StacOverflowError
不固定大小,如果溢出,OOM
2.不能,只能延缓栈溢出的时间
3,不是,太大会影响其他的存储空间,也可能会影响线程数量,每个线程的栈太大,导致线程数量变少
4.不会涉及,只有方法区和堆存在垃圾回收
5.具体问题具体分析,如果一个线程不安全的变量定义在了方法内部,而且没有返回的话,就是线程安全的,因为这个变量只属于当前栈帧,其他线程无法共享该变量。

    //s1的声明方式是线程安全的
    public static void method1(){
        //StringBuilder:线程不安全
        //在方法内部定义,在方法内部消亡。
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
    }
    //sBuilder的操作过程:是线程不安全的
    public static void method2(StringBuilder sBuilder){
        //方法传参,线程不安全。因为多个线程调用可以同时传入这个参数。
        sBuilder.append("a");
    }
    //s1的操作:是线程不安全的
    public static StringBuilder method3(){
        //作为方法返回值,也可以由多个线程调用,线程不安全
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        return s1;
    }
    //s1的操作:是线程安全的
    public static String method4(){
        //返回tostring 线程安全,因为string是new出来的,和StringBuilder已经没有关系了
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        return s1.toString();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值