前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战:OutOfMemoryError 异常(一) -- 虚拟机栈和本地方法栈溢出

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

作者头像
Li_XiaoJin
发布2022-06-10 21:04:03
3350
发布2022-06-10 21:04:03
举报
文章被收录于专栏:Lixj's BlogLixj's Blog

关于虚拟机栈和本地方法栈溢出情况

异常介绍

由于在 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于 HotSpot 来说,虽然 -Xoss 参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由 -Xss 参数设定。

关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。

单线程情况

将实验范围限制于单线程中的操作,尝试了下面两种方法均无法让虚拟机产生 OutOfMemoryError 异常,尝试的结果都是获得 StackOverflowError 异常。

  1. 使用 -Xss 参数减少栈内存容量。结果:抛出 StackOverflowError 异常,异常出现时输出的堆栈深度相应缩小。
  2. 定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果:抛出 StackOverflowError 异常时输出的堆栈深度相应缩小。

代码如下:

代码语言:javascript
复制
/**
 * -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;
        }

    }
}

运行结果:

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

多线程情况

如果测试时不限于单线程,通过不断地建立线程的方式倒是可以产生内存溢出异常。但是这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,或者准确地说,在这种情况下,为每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。

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

代码语言:javascript
复制
/**
 * -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();
    }
}

结果:

正常情况下回出现,也有可能系统会卡死。

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

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

书籍介绍:《深入理解Java虚拟机:JVM高级特性与最佳实践》

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可 Links: https://lixj.fun/archives/实战outofmemoryerror异常一--虚拟机栈和本地方法栈溢出

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 异常介绍
  • 单线程情况
  • 多线程情况
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档