02、JVM实战:内存区域:栈

02–内存区域–栈


1、结构图

*

2、虚拟机栈(java栈)

2.1、特征

1、 线程私有;
2、 后进先出(LIFO)栈;
3、 线程请求的栈深度大于虚拟机允许的深度,出现StackOverflowError异常;
4、 虚拟机在扩展栈时无法申请到足够的内存空间,出现OutOfMemoryError异常;
5、 和本地方法栈区别:虚拟机栈描述的是Java方法执行的内存模型,存储栈帧,支撑Java方法的调用、执行和退出;

2.2、栈帧

*

2.2.1、概念和特征

1、 在java栈中保存的主要内容为栈帧;
2、 一个完整的栈帧包含:局部变量表、操作数栈、动态连接信息、方法正常完成和异常完成信息;
3、 每一次函数调用,都会有一个对应的栈帧被压入java栈,每一个函数调用结束,都会有一个栈帧被弹出java栈如上图:栈帧和函数调用函数1对应栈帧1,函数2对应栈帧2,依次类推函数1中调用函数2,函数2中调用函数3,函数3调用函数4.当函数1被调用时,栈帧1入栈,当函数2调用时,栈帧2入栈,当函数3被调用时,栈帧3入栈,当函数4被调用时,栈帧4入栈;
4、 当前正在执行的函数所对应的帧就是当前帧(位于栈顶),它保存着当前函数的局部变量、中间计算结果等数据;

2.2.2、在概念模型上,典型的栈帧结构如图所示

*
*
*

2.3、局部变量表概念和特征

1、 由若干个Slot(局部变量空间)组成,长度由编译期决定;
2、 单个Slot可以存储一个类型为boolean、byte、char、short、float、reference(对象引用)和returnAddress(类型)的数据两个Slot可以存储一个类型为long或double(64位长度)的数据;
3、 用于保存方法的参数以及局部变量,局部变量表中的变量只在当前函数调用中有效,当函数调用结束,随着函数栈帧的弹出销毁,局部变量表也会随之销毁;
4、 对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用;
5、 局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的;
6、 通过索引访问;

2.4、方法返回地址概念

1、 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址;
2、 由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰也就解释了栈是线程私有的;

2.5、操作数栈的概念和特征

1、 是一个后进先出栈,由若干个Entry组成,长度由编译期决定;
2、 单个Entry即可以存储一个Java虚拟机中定义的任意数据类型的值,包括long和double类型,但是存储long和double类型的Entry深度为2,其他类型的深度为1;
3、 和局部变量区一样,操作数栈也是被组织成一个以字节为单位的数组;
4、 通过标准的栈操作(压栈和出栈)来访问的;
5、 程序中的所有计算过程都是在借助于操作数栈来完成的;

2.6、StackOverflowError(可通过-Xss来调教栈深度)

由于每次函数调用都会产生对应的栈帧,从而占用一定的栈空间,因此,如果栈空间不足,那么函数调用自然无法继续进行下去。

当请求的栈深度大于最大可用栈深度时,系统会抛出StackOverflowError栈溢出错误。

package com.fei.zhou.day1;

public class StackOverflowErrorTest {

	private static int count = 0;

	public static void main(String[] args) {
		try {
			test();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}

	}

	private static void test() {
		// 描述:使用递归,由于递归没有出口,这段代码可能会抛出栈溢出错误
		System.out.println("最大的调用深度:" + (count++));
		test();
	}
}
结果:
最大的调用深度:12554
Exception in thread "main" java.lang.StackOverflowError
	at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
	at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)
	at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)
	at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:561)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.write(PrintStream.java:526)
	at java.io.PrintStream.print(PrintStream.java:669)
	at java.io.PrintStream.println(PrintStream.java:806)
	at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:19)
	at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)
	at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)
	at com.fei.zhou.day1.StackOverflowErrorTest.test(StackOverflowErrorTest.java:20)

2.7、OutOfMemoryError

我这就不测试了,只要调小内存大小,再来一个无限增加堆内存的方法,就会抛出该异常

3、本地方法栈(native method stack)

3.1、本地方法栈的特征

1、 线程私有;
2、 后进先出(LIFO)栈;
3、 可能出现OutOfMemoryError异常和StackOverflowError异常;
4、 有一些虚拟机(如SunHotSpot)将Java虚拟机栈和本地方法栈合二为一;
5、 和虚拟机栈区别:作用是支撑虚拟机使用的Native方法的调用、执行和退出;