JVM之虚拟机栈

时间:2022-12-29 09:42:40

虚拟机栈是一种可以被用来快速访问的存储区域,该区域位于通用RAM[1]里面,通过使用它的所谓的“栈指针”可以让我们访问处理器。栈是一种快速有效的分配存储方法,存取速度仅次于寄存器,堆栈指针若向下移动,则分配新的内存,若向上移动,则释放那些内存。由于Java编译器需要预先去生成相应的内存空间,所以当我们尝试创建程序的时候,Java编译器必须知道被存储在栈内的所有数据的确切大小和生命周期,以便可以按照上面陈述的分配存储方法通过上下移动堆栈指针来动态调整内存空间。我们可以认为这一约束限制了程序的灵活性,所以这就是为什么只有某些Java数据,特别是对象引用,它被存储在栈里面,而应用程序内部数量庞大的Java对象没有被存储在虚拟机栈里面。


总的来说,栈的优势是存取速度比堆要快,它仅次于寄存器,并且栈数据是可以被共享的。栈的缺点是存储在栈里面的数据大小与生存期必须是确定的,从这一点来看,栈明显缺乏灵活性。虚拟机栈中主要被用来存放一些基本类型的变量,例如int、short、long、byte、float、double、boolean、char,以及对象引用。


我们前面说过,虚拟机栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 1;

int b = 1;

对于上面的代码,编译器处理第一条语句,首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有1这个值,如果没找到,就将1存放进来,然后将a指向1。接下来处理第二条语句,在创建完b的引用变量后,因为在栈中已经有1这个值,便将b直接指向1。这样,就出现了a与b同时均指向1的情况。这时,如果存在第三条语句,它针对a再次定义为a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享的方式存在明显的不同,因为这种情况a的修改并不会影响到b,它是由编译器完成的,这样的做法有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。


与程序计数器一样,Java虚拟机栈也是线程私有的内存空间,它和Java线程在同一时间创建,它保存方法的局部变量、部分结果,并参与方法的调用和返回。


Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。在Java虚拟机规范中定义了两种异常与栈空间有关,即*Error和OutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则程序运行过程中会抛出*Error异常。如果Java栈可以动态扩展,而在扩展栈的过程中没有足够的内存空间来支持栈的发展,则程序运行过程中会抛出OutOfMemeoryError异常。


我们可以使用-XSS参数来设置虚拟机栈的大小,栈的大小直接决定了函数调用的可达深度。


清单2-1所示代码展示了一个递归调用的应用。计数器COUNT记录了递归的层次,recusion方法是一个没有出口的递归函数,通过testStack方法的调用,该函数会不断地申请栈的深度,最终程序一定会导致虚拟机栈溢出。为了方便记录测试数据,程序会在第13行代码中当栈溢出异常发生时打印出虚拟机栈的当前深度。


代码清单2-1 虚拟机栈示例代码TestJVMStack

public class TestJVMStack {

     privateint count = 0;

     //没有出口的递归函数

     publicvoid recursion(){

  count++;//每次调用深度加1

       recursion();//递归

     }

 

     publicvoid testStack(){

         try{

            recursion();

        }catch(Throwable e){

            System.out.println("deep of stack is "+count);//打印栈溢出的深度

            e.printStackTrace();

         }

     }

     publicstatic void main(String[] args){

        TestStack ts = new TestStack();

        ts.testStack();

     }

}

清单2-1程序输出如清单2-2所示,笔者本机运行的程序最终在申请到9013层虚拟机栈时,抛出异常。

代码清单2-2 代码2-1程序运行输出

    java.lang.*Error

    at TestStack.recursion(TestStack.java:7)

    at TestStack.recursion(TestStack.java:7)

    at TestStack.recursion(TestStack.java:7)

    at TestStack.recursion(TestStack.java:7)

    at TestStack.recursion(TestStack.java:7)

    at TestStack.recursion(TestStack.java:7)

    atTestStack.recursion(TestStack.java:7)deep of stack is 9013


如果系统需要支持更深的栈调用,则可以使用参数-Xss1M运行程序,可以扩大虚拟机栈的大小,刚才的配置扩大栈空间的最大值为1MB。



[1]    随机存取存储器(random access memory,RAM)又称作“随机存储器”,是与CPU直接交换数据的内部存储器,也叫主存(内存)。它可以随时读写,而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。



JVM之虚拟机栈欢迎关注麦克叔叔每晚十点说,感兴趣的朋友可以一起交流与学习。