虚拟机栈是线程执行Java程序时,处理Java方法中内容的内存区域。虚拟机栈也是线程私有的区域,每个Java方法被调用的时候,都会在虚拟机栈中创建出一个栈帧。如图,栈帧又由局部变量表、操作数栈、动态链接和方法返回四部分组成,有些虚拟机的栈帧还包括了一些附加信息。
一、虚拟机栈运行原理
如图,线程执行Java方法时,从Java方法1开始依次调用,每调用一个方法,就会有入栈操作,向虚拟机栈中压入一个栈帧,线程当前正在执行的Java方法对应了栈顶栈帧(当前栈帧),当前方法执行完成后,当前方法的执行结果传给下一个栈帧,之后进行出栈操作,丢弃当前栈帧(无论方法有没有产生异常,都会丢弃),下一个栈帧就会称为新的栈顶栈帧,从之前调用已执行完方法的位置继续执行。虚拟机栈是线程的私有区域,并且栈帧不允许被其他线程访问,所以不存在线程安全问题,栈帧弹出后就内存就会被系统回收,所以不也存在垃圾回收问题。
栈结构处理Java方法的优势:
- 1、速度快,栈分配存储的速度仅次于程序计数器。
- 2、操作简便,线程执行Java方法时,只有入栈和出栈操作,调用时入栈,执行结束时出栈。
- 3、不存在垃圾回收问题。
二、虚拟机栈大小设置
1、设置虚拟栈的参数
虚拟栈的大小可以通过-Xss参数设置,默认单位是byte,也可以使用不区分大小写k,m,g作为单位(如-Xss1m),如下不同系统下的-Xss默认值不同
- Linux: 1024k
- MacOs: 1024k
- Windows: 默认值依赖于虚拟机的内存。
2、虚拟机栈的异常
- java.lang.StackOverflowError异常:如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出StackOverflowError异常,用下面代码可以验证异常。
/**- * * JVM参数: -Xss512k **/ public class StackOverflowErrorDemo { private static int i = 0; // 方法调用次数 public static void main(String[] args) { method1(); } public static void method1() { System.out.println(i++); method1(); } }
- java.lang.OutofMemoryError异常:
(1)如果Java虚拟机栈允许动态扩展,并且在尝试扩展的时候无法申请到足够的内存,就会抛出OutofMemoryError异常。《Java虚拟机规范》中允许虚拟机自行选择是否支持动态扩展,HotSpot虚拟机选择不支持,所以我们使用的openJDK去用-Xss参数设小栈内存或者创建大量的局部变量的方式去测试时,HotSpot虚拟机抛出的始终是StackOverflowError异常。并且栈容量最小值是有限制的,主要取决操作系统的内存分页。像早期的Classic虚拟机支持动态扩展栈内存,就会出现OOM异常。
(2)不断创建新的线程,当创建线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutofMemoryError异常。原因是操作系统分配给JVM进程的内存是有限的,而这部分内存堆区和方法区占据大部分内存,当方法区和堆区所占内存越大,可供线程用来分配给虚拟机栈的内存就越少,每个线程都有私有的虚拟机栈,不断创建线程,当可用内存耗尽,新的线程无法申请到内存给虚拟机栈,就会出现OutofMemoryError异常。下面的验证代码摘抄自《深入理解Java虚拟机》,对系统压力较大,谨慎执行。
/** * * VM args: -Xss2M (设置大一些,在32位系统下运行) **/ public class JavaVMStackOOM { private void dontStop() { while (true) { } } private void stackLeakByThread() { while (true) { Thread thread = new Thread(new Runnable() { @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }