Java教程

day10-栈

本文主要是介绍day10-栈,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

day10-栈

栈对应线程,栈帧对应线程中的方法

image-20210715190639129

栈对应线程,栈帧对应线程中的方法

为什么main()先执行最后结束

因为main方法是系统启动的入口,所以main方法是最先压入栈底的,由于栈遵循的是先进后出的原则,所以main方法最先进去,最后出来,main方法的弹出也就以为这线程的结束

栈中存放的数据类型?

八大基本类型,对象引用,调用的实例方法

栈满了会弹出什么错误提示?

StackOverflowError:栈溢出错误,系统出现异常不会终止,出现错误回终止运行。

栈,堆,方法区的交互关系

image-20210715192636895

JVM中有两种栈:java虚拟机栈和本地方法栈

java虚拟机栈:为jVM中执行java方法服务

本地方法栈:为JVM中执行native方法服务

什么是native?

JDk中有很对方法是用native关键字修饰的,被native修饰的方法说明该方法不在jvm的能力范围内,不是用java语言实现的,而是用c或者c++来实现的,这些方法被放到本地方法栈中,在使用时它会调用java本地方法的接口(java native Interface),本地方法接口再到本地方法库中调用相应方法。

为什么要使用到c或c++语言呢?

因为在java诞生的初期,正式c语言和c++语言盛行的时候,所以想要有一席之地,就必须与它兼容,调用它的一些接口来实现,所以就诞生了本地方法库

什么是栈?

栈就是限定仅在表头插入和删除得线性表,遵循后进先出的原则,一个线程中执行的方法,会根据执行的先后顺序压入栈中,执行完最后压入的方法,再弹出,以此类推。

栈是线程私有的,就是每个线程都会有一个自己独立的栈,栈的生命周期与线程共存亡,每个线程都有自己独立的栈。

image-20210716002538987

栈中存放的是什么?

栈中的存放单位是栈帧,每个方法的执行都意味着一个新的栈帧被压入栈中。每个方法的调用到结束,就标着一个栈帧从压入到弹出。

每个栈帧中存放着什么?

局部变量表、操作数栈、动态链接和返回地址等信息。

image-20210716003541763

局部变量表:

栈帧中,由一个局部变量表来存储数据。

局部变量表中存储什么?
  • 八大基本数据类型的局部变量(包括参数):byte,boolean,char,short,int,float,double,long.
  • 对象的引用:String、数组、对象等,但是不存储对象的内容
局部变量表的内存空间?

局部变量表的内存空间,在编译时就已经完成分配了,在方法运行期间不会改变局部变量表的内存大小。

局部变量表的容量?

局部变量表的容量是以**变量槽(Variable Slot)**为最小单位的,每个变量槽能够存储最大32位的数据类型,而对于64位的数据类型(long,double),局部变量表会用两个连续的变量槽来存储。

JVM如何使用局部变量表?

JVM以索引定位的方式使用局部变量表,索引的范围从0开始到最大的变量槽数(Vriabale Slot)。

普通方法与static方法栈帧中,局部变量表变量槽的区别?

非static方法在第零个变量槽中存放的是方法所属实例对象的引用。

Slot可以复用吗?

局部变量表中的变量槽可以复用,方法中定义的局部变量,不一定覆盖了方法的整个作用域,当方法运行时,如果已经超过了某个变量的作用域,该变量就失效了,这个变量所使用的slot就可以被后边的变量使用,也就是复用。

Sloat复用的示例?
public void test(boolean flag)
{
    if(flag)
    /*{
        int a = 66;
    }*/a的作用域
    
    int b = 55;
}

当虚拟机运行test方法时,就会在栈中创建一个栈帧,当运行到int a=66;时,就会在栈帧的局部变量表中创建一个变量槽,a的作用域就是在if的中括号中,当运行到int b=55;时,a的作用域失效,b就可以占用a的slot。

Sloat复用的弊端?

影响垃圾回收的行为,可能一个变量已经失效,但是没有新的变量占用它的空间时,它就可能不会被gc回收。

public class TestDemo {
 
    public static void main(String[] args){
        
        byte[] placeholder = new byte[64 * 1024 * 1024];
        
        System.gc();
    }
}

代码解释:在堆中创建64M个byte数组,占用64M个slot局部变量表空间。

查看gc容量:-verbose:gc;

image-20210716092922193

image-20210716094418527

当我们运行gc是并没有回收变量placeholder,是因为 变量跟System.gc在同一个作用域,正在引用的变量不会被回收。

下一步,将System.gc与变量分开

public class TestDemo {
 
    public static void main(String[] args){
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }
}

执行-verbose:gc

image-20210716093948141

还是没有回收,在回收之前,加一个变量。

public class TestDemo {
 
    public static void main(String[] args){
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int a = 0;
        System.gc();
    }
}

执行-verbose:gc

image-20210716094124582

由于在placeholder作用域外边,又出现了一个新的变量,所以新的变量就会覆盖掉已经超过作用域失效的变量,从而达到slot复用。

placeholder 变量能否被回收的关键就在于:局部变量表中的 Slot 是否还存有关于 placeholder 对象的引用。

第一次修改中,限定了 placeholder 的作用域,但之后并没有任何对局部变量表的读写操作,placeholder 变量在局部变量表中占用的Slot没有被其它变量所复用,所以作为 GC Roots 一部分的局部变量表仍然保持着对它的关联。所以 placeholder 变量没有被回收。

第二次修改后,运行到 int a = 0 时,已经超过了 placeholder 变量的作用域,此时 placeholder 在局部变量表中占用的Slot可以交给其他变量使用。而变量a正好复用了 placeholder 占用的 Slot,至此局部变量表中的 Slot 已经没有 placeholder 的引用了,虚拟机就回收了placeholder 占用的 64M 内存空间。

操作数栈:

一般栈都是后进先出的,所以操作数栈也是,操作数栈可以是任意的java数据类型,方法刚执行时,操作数栈是空的,在方法的执行过程中,通过字节码指令对操作数栈进行压栈和出栈的操作,通常进行算数运算的时候是通过操作数栈进行的,又或者是在调用其他方法的时候通过操作数栈进行参数传递。操作数栈可以理解为栈帧中用于计算的临时数据存储区。

public class OperandStack{
 
    public static int add(int a, int b){
        int c = a + b;
        return c;
    }
 
    public static void main(String[] args){
        add(100, 98);
    }
}

使用 javap 反编译 OperandStack 后,根据虚拟机指令集,得出操作数栈的运行流程如下:

image-20210716095708124

add 方法刚开始执行时,操作数栈是空的。当执行 iload_0 时,把局部变量 0 压栈,即 100 入操作数栈。然后执行 iload_1,把局部变量1压栈,即 98 入操作数栈。接着执行 iadd,弹出两个变量(100 和 98 出操作数栈),对 100 和 98 进行求和,然后将结果 198 压栈。然后执行 istore_2,弹出结果(出栈)。

下面通过一张图,对比执行100+98操作,局部变量表和操作数栈的变化情况。

image-20210716100114356

原文链接:https://blog.csdn.net/rongtaoup/article/details/89142396

栈中会出现什么异常?

StackOverflowError:栈溢出错误

如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError

OutofMemoryError:内存不足错误

栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。

如何设置栈的参数?

使用**-Xss**设置栈的大小,通常几百k即可,因为栈是线程私有的,所以线程越多,占用的总的栈空间越大。

image-20210716102335527

栈决定了什么?

栈决定了调用函数的深度,比如递归,就是不断调用方法本身,也就是不断的向虚拟机栈中压入栈帧,所以如果没有一个递归头来表示递归的结尾,或者递归的次数过于庞大时,可能就会将栈分配的内存加满,从而报出StackOverflowError错误。

这篇关于day10-栈的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!