《深入理解 Java 虚拟机》学习笔记 Day7(OutOfMemoryError 异常实战:Java 堆溢出)

学习内容:第2章 - Java 内存区域与内存溢出异常

实战代码均在我的 Git 仓库中:https://github.com/nx-xn2002/JVM-Learn.git

虚拟机栈和本地方法栈溢出

HotSpot 虚拟机中不区分虚拟机栈和本地方法栈,因此对于 HotSpot 来说,设置本地方法栈大小的参数 -Xoss 虽然存在,实际上却没有任何效果,栈容量只能使用 -Xss 参数来设定

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

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常
  • 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError 异常

Java 虚拟机可以实现自行选择是否支持栈的动态扩展,而 HotSpot 虚拟机的选择是不支持扩展,因此只有在创建线程申请内存时就因为无法获取到足够内存而出现 OutOfMemoryError 异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致 StackOverflowError 异常

在此,作者设计了两个实验来进行验证在单线程中操作,这些行为是否能够让 HotSpot 虚拟机产生OutOfMemoryError 异常:

  • 实验一:使用 -Xss 参数减少栈内存容量
    预计结果:抛出 StackOverflowError 异常,异常出现时,输出的堆栈深度相应缩小
  • 实验二:定义大量的本地变量,增大当前方法帧中本地变量表的长度
    预计结果:抛出 StackOverflowError 异常,异常出现时,输出的堆栈深度相应缩小

实验一:使用 -Xss 参数减少栈内存容量

按照书中内容,我使用了以下代码进行运行:

/**
 * Java 虚拟机栈 StackOverflowError实验一:使用 -Xss 参数减少栈内存容量
 * VM Options:-Xss128k
 *
 * @author Ni Xiang
 */
public class JavaVMStackSOF1 {
    private int stackLength = 0;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF1 oom = new JavaVMStackSOF1();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

将 VM Options 设置为 -Xss128k 后,运行得到以下结果,抛出 StackOverflowError 异常:
在这里插入图片描述
而当设置为 256k 后,可见栈深度增大了:
在这里插入图片描述

注意事项

作者在书中指出,对于不同版本的 Java 虚拟机和不同的操作系统,栈容量最小值可能会有所限制。对此,我进行了尝试:
在这里插入图片描述
我使用的是 64 位 Windows 系统下的 JDK 17,由此可见,我的栈容量最小值是 84k

实验二:定义大量的本地变量,增大当前方法帧中本地变量表的长度

验证第二种情况,使用如下代码,定义大量变量来多占局部变量表空间

/**
 * Java 虚拟机栈 StackOverflowError实验二:定义大量的本地变量,增大当前方法帧中本地变量表的长度
 *
 * @author Ni Xiang
 */
public class JavaVMStackSOF2 {
    private static int stackLength = 0;

    public static void stackLeak() {
        long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11,
                unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21,
                unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31,
                unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41,
                unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51,
                unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61,
                unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71,
                unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81,
                unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91,
                unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        stackLeak();
        unused1= unused2= unused3= unused4= unused5= unused6= unused7= unused8= unused9= unused10= unused11=
                unused12= unused13= unused14= unused15= unused16= unused17= unused18= unused19= unused20= unused21=
                unused22= unused23= unused24= unused25= unused26= unused27= unused28= unused29= unused30= unused31=
                unused32= unused33= unused34= unused35= unused36= unused37= unused38= unused39= unused40= unused41=
                unused42= unused43= unused44= unused45= unused46= unused47= unused48= unused49= unused50= unused51=
                unused52= unused53= unused54= unused55= unused56= unused57= unused58= unused59= unused60= unused61=
                unused62= unused63= unused64= unused65= unused66= unused67= unused68= unused69= unused70= unused71=
                unused72= unused73= unused74= unused75= unused76= unused77= unused78= unused79= unused80= unused81=
                unused82= unused83= unused84= unused85= unused86= unused87= unused88= unused89= unused90= unused91=
                unused92= unused93= unused94= unused95= unused96= unused97= unused98= unused99= unused100=0;

    }

    public static void main(String[] args) throws Throwable {
        try {
            stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

结果如下,依然是抛出 StackOverflowError 异常:
在这里插入图片描述

结论

上述两个实验表明,无论是由于栈帧太大还是虚拟机容量太小,当新的栈帧内存无法分配的时候,HotSpot 虚拟机抛出的都是 StackOverflowError 异常

HotSpot 创建线程导致内存溢出异常

如果测试的时候不限于单线程,通过不断创建线程的方法,在 HotSpot 上也可以产生内存溢出异常。但这样产生的内存溢出异常和占空间是否足够并不存在任何直接的关系,主要取决于操作系统本身的内存使用状态。在这种情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
实践代码如下:

/**
 * Java 虚拟机栈 OutOfMemory - 创建线程导致的内存溢出异常
 * VM Options:-Xss2M
 *
 * @author Ni Xiang
 */
public class JavaVMStackOOM {
    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) throws Throwable{
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

将虚拟机栈内存的大小设置为了 2M,运行后出现了作者预警的严重系统卡顿的情况,但在此之后,并没有出现预期的 OOM 抛出。尝试几次后,也尝试过将内存大小增大至 512M,依然无效。

暂未找到原因,看作者文内一直说的是在 32 位的 Windows 上,可能是系统的差异?或者可能只能归结于概率事件吧,有知道的老哥可以在评论区说一下

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值