商城首页欢迎来到中国正版软件门户

您的位置:首页 >虚拟机栈溢出实战解析

虚拟机栈溢出实战解析

  发布于2025-07-29 阅读(0)

扫一扫,手机访问

在HotSpot虚拟机中,由于不区分虚拟机栈和本地方法栈,因此-Xoss参数(用于设置本地方法栈大小)实际上是无效的,栈容量仅由-Xss参数设定。

Java虚拟机规范中提到了两种与虚拟机栈和本地方法栈相关的异常:

如果线程请求的栈深度超过虚拟机允许的最大深度,将会抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则会抛出OutOfMemoryError异常。在单线程情况下,尝试了以下两种方法,但都无法引发OutOfMemoryError异常,结果都是抛出了StackOverflowError异常:

  1. 使用-Xss参数减少栈内存容量。结果:抛出StackOverflowError异常,且异常出现时输出的堆栈深度相应缩小。
  2. 定义大量本地变量,增加方法帧中本地变量表的长度。结果:抛出StackOverflowError异常时,输出的堆栈深度相应缩小。代码如下:
/** * -Xss128k */
public class JavaVmStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) {
        JavaVmStackSOF vm = new JavaVmStackSOF();
        try {
            vm.stackLeak();
        } catch (Throwable e) {
            System.out.println("stackLength:" + vm.stackLength);
            throw e;
        }
    }
}

运行结果:

实战:OutOfMemoryError 异常(一) -- 虚拟机栈和本地方法栈溢出

由此可以看出,在单线程下,无论是由于栈帧过大还是虚拟机栈容量过小,当内存无法分配时,虚拟机抛出的都是StackOverflowError异常。

在多线程情况下,如果测试不限于单线程,通过不断建立线程的方式确实可以引发内存溢出异常。但这种情况下产生的内存溢出异常与栈空间大小没有直接关系,或者更准确地说,为每个线程分配的栈内存越大,反而越容易引发内存溢出异常。

代码如下:注意:在Windows平台的虚拟机中,Java线程映射到操作系统的内核线程,运行以下代码有较大风险,可能导致操作系统卡死。请谨慎运行!(我已经试验过,电脑会卡死T_T)

/** * -Xss2M */
public class JavaStackOOM {
    private void dontStop() {
        while(true) {
        }
    }
    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) {
        JavaStackOOM oom = new JavaStackOOM();
        oom.stackLeakByThread();
    }
}

结果:

实战:OutOfMemoryError 异常(一) -- 虚拟机栈和本地方法栈溢出

原因不难理解,操作系统为每个进程分配的内存是有限的,例如32位Windows限制为2GB。虚拟机提供了参数来控制Java堆和方法区的最大值。剩余的内存为2GB(操作系统限制)减去Xmx(最大堆容量),再减去MaxPermSize(最大方法区容量),程序计数器消耗的内存可以忽略不计。如果不计算虚拟机进程本身消耗的内存,剩下的内存就由虚拟机栈和本地方法栈共享。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易耗尽剩余内存。

在开发多线程应用时要特别注意,出现StackOverflowError异常时有错误堆栈可以阅读,相对来说,比较容易找到问题所在。而且,如果使用虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小不同,所以只能说在大多数情况下)达到1000~2000完全没有问题,对于正常的方法调用(包括递归),这个深度应该完全足够。但是,如果是由于建立过多线程导致的内存溢出,在不能减少线程数或更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的处理经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到。

本文转载于:https://cloud.tencent.com/developer/article/2020499 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注