栈对应线程,栈帧对应线程中的方法
栈对应线程,栈帧对应线程中的方法
为什么main()先执行最后结束?
因为main方法是系统启动的入口,所以main方法是最先压入栈底的,由于栈遵循的是先进后出的原则,所以main方法最先进去,最后出来,main方法的弹出也就以为这线程的结束
栈中存放的数据类型?
八大基本类型,对象引用,调用的实例方法
栈满了会弹出什么错误提示?
StackOverflowError:栈溢出错误,系统出现异常不会终止,出现错误回终止运行。
JVM中有两种栈:java虚拟机栈和本地方法栈
java虚拟机栈:为jVM中执行java方法服务
本地方法栈:为JVM中执行native方法服务
JDk中有很对方法是用native关键字修饰的,被native修饰的方法说明该方法不在jvm的能力范围内,不是用java语言实现的,而是用c或者c++来实现的,这些方法被放到本地方法栈中,在使用时它会调用java本地方法的接口(java native Interface),本地方法接口再到本地方法库中调用相应方法。
因为在java诞生的初期,正式c语言和c++语言盛行的时候,所以想要有一席之地,就必须与它兼容,调用它的一些接口来实现,所以就诞生了本地方法库
栈就是限定仅在表头插入和删除得线性表,遵循后进先出的原则,一个线程中执行的方法,会根据执行的先后顺序压入栈中,执行完最后压入的方法,再弹出,以此类推。
栈是线程私有的,就是每个线程都会有一个自己独立的栈,栈的生命周期与线程共存亡,每个线程都有自己独立的栈。
栈中的存放单位是栈帧,每个方法的执行都意味着一个新的栈帧被压入栈中。每个方法的调用到结束,就标着一个栈帧从压入到弹出。
局部变量表、操作数栈、动态链接和返回地址等信息。
栈帧中,由一个局部变量表来存储数据。
局部变量表的内存空间,在编译时就已经完成分配了,在方法运行期间不会改变局部变量表的内存大小。
局部变量表的容量是以**变量槽(Variable Slot)**为最小单位的,每个变量槽能够存储最大32位的数据类型,而对于64位的数据类型(long,double),局部变量表会用两个连续的变量槽来存储。
JVM以索引定位的方式使用局部变量表,索引的范围从0开始到最大的变量槽数(Vriabale Slot)。
非static方法在第零个变量槽中存放的是方法所属实例对象的引用。
局部变量表中的变量槽可以复用,方法中定义的局部变量,不一定覆盖了方法的整个作用域,当方法运行时,如果已经超过了某个变量的作用域,该变量就失效了,这个变量所使用的slot就可以被后边的变量使用,也就是复用。
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。
影响垃圾回收的行为,可能一个变量已经失效,但是没有新的变量占用它的空间时,它就可能不会被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;
当我们运行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
还是没有回收,在回收之前,加一个变量。
public class TestDemo { public static void main(String[] args){ { byte[] placeholder = new byte[64 * 1024 * 1024]; } int a = 0; System.gc(); } }
执行-verbose:gc
由于在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 后,根据虚拟机指令集,得出操作数栈的运行流程如下:
add 方法刚开始执行时,操作数栈是空的。当执行 iload_0 时,把局部变量 0 压栈,即 100 入操作数栈。然后执行 iload_1,把局部变量1压栈,即 98 入操作数栈。接着执行 iadd,弹出两个变量(100 和 98 出操作数栈),对 100 和 98 进行求和,然后将结果 198 压栈。然后执行 istore_2,弹出结果(出栈)。
下面通过一张图,对比执行100+98操作,局部变量表和操作数栈的变化情况。
原文链接:https://blog.csdn.net/rongtaoup/article/details/89142396
StackOverflowError:栈溢出错误
如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError
OutofMemoryError:内存不足错误
栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
使用**-Xss**设置栈的大小,通常几百k即可,因为栈是线程私有的,所以线程越多,占用的总的栈空间越大。
栈决定了调用函数的深度,比如递归,就是不断调用方法本身,也就是不断的向虚拟机栈中压入栈帧,所以如果没有一个递归头来表示递归的结尾,或者递归的次数过于庞大时,可能就会将栈分配的内存加满,从而报出StackOverflowError错误。