Java内存溢出异常(上)

Java内存溢出异常主要分为两类:内存溢出和堆栈溢出。在下列情况下,将引发内存异常:Java堆溢出、虚拟机堆栈和本地方法栈溢出、方法区域和运行时常数池溢出以及本地直接内存溢出。以下各节将逐一描述这些类型的异常。

Java堆溢出

正如Java内存区域和内存溢出异常中所提到的,Java堆主要用于存储对象实例。这部分内存区域的大小可以由-xms参数和-xmx参数设置。通常将-xms和-xmx的值设置为相同的值,以减少内存扩展或收缩的开销。

Java堆在空间上受到限制,同时受到物理内存和虚拟机内存的限制(虚拟机内存通常设置为小于物理内存)。因此,如果对象实例数量增加,垃圾收集机制不及时清理,对象实例占用的空间将达到Java堆的最大值。此时,由于Java堆内存不足,引发了OutOfMeMyLogError异常,这使得不可能为新实例分配空间。

通过设置-Xms20m -Xmx20m运行以下代码可以模拟这一情况:

运行结果:

java.lang.OutOfMemoryError: Java heap space

这是一个常见的OOM异常。对于这种异常,在打印异常信息时通常会引发异常的原因,如图中所示的“Java堆空间”。当然,仅此信息还不足以确定内存容量设置是否很小或是否存在内存泄漏(有关内存泄漏的知识将在后面的文章中介绍)。因此,我们需要通过添加-xx:+heapDumpOnAutofMemoryError参数等其他方式进一步查明问题的根本原因,使dump在虚拟机发生内存溢出异常时,取当前内存堆转储快照,并用相关工具进行分析。这类知识在本文中不会解释得太多,但将在下面的文章中逐一介绍。

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

为什么要一起讨论虚拟机堆栈和本地方法堆栈的溢出,因为热点虚拟机中的虚拟机堆栈和本地方法堆栈没有区别。对于Hotspot,虽然-xoss参数用于设置本地方法堆栈的大小,但它实际上是无效的,堆栈的容量仅由-xss参数设置。

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

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

这种分类不是很清楚,因为太少的内存或太多的堆栈空间会导致堆栈空间无法继续分配。

堆栈溢出错误是一个简单的条件。堆栈溢出错误是以下简单代码中的堆栈溢出:堆栈溢出错误、堆栈溢出错误、堆栈溢出错误、堆栈溢出错误、堆栈溢出错误、堆栈溢出错误、堆栈溢出错误


public class JavaVMStackSOF {
    private int stackLength = 1;

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

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

运行结果如下:

Stack length:18663
Exception in thread "main" java.lang.StackOverflowError
	at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
	at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
	at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
	at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
    ......

对于上述结果,不同计算机的堆栈长度大小是不确定的。从输出异常信息来看,这是因为stackleak方法具有太多的递归调用层。在大多数情况下,在虚拟机的默认参数下,堆栈深度足够。

OutofMemoryError异常相对比较难发生,通常在多线程环境中。创建线程时,虚拟机会会将私有堆栈空间分配给相应的线程,该线程的大小可以用-xss参数设置。通过不断地创建新进程,可以生成内存溢出异常。

原因是当一个进程运行时,操作系统分配给该进程的内存是有限的。Java堆和方法区域占大多数内存,忽略程序计数器占用的一小部分内存,而不计算虚拟机本身占用的内存,其余部分由虚拟机栈和本地方法栈占据。因此,当创建的线程数达到一定水平时,虚拟机堆栈和本地方法堆栈占用的空间会使进程的内存空间不足,从而抛出内存溢出异常。

这部分的测试代码如下:


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) {
        JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
        javaVMStackOOM.stackLeakByThread();
    }
}

此代码运行有一定风险,因为Java线程不是完全用户级线程,有部分映射到操作系统,因此可能导致系统假死,请谨慎运行。

运行结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

由此可以看出,在开发多线程时,我们应该对线程的数量有一定的保证,线程池的重用是非常必要的。

未完待续~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员-张凯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值