📜  了解Java中的 OutOfMemoryError 异常

📅  最后修改于: 2022-05-13 01:55:01.856000             🧑  作者: Mango

了解Java中的 OutOfMemoryError 异常

在Java中,所有对象都存储在堆中。它们是使用 new运算符分配的。 Java中的 OutOfMemoryError 异常如下所示:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

通常,当Java虚拟机因为内存不足而无法分配对象时会引发此错误。垃圾收集器无法提供更多内存。

OutOfMemoryError通常意味着您做错了什么,要么持有对象太久,要么试图一次处理太多数据。有时,它表示您无法控制的问题,例如缓存字符串的第三方库或部署后未清理的应用程序服务器。有时,它与堆上的对象无关。

当无法满足本机分配时(例如,如果交换空间不足),本机库代码也可能引发Java.lang.OutOfMemoryError 异常。让我们了解可能发生 OutOfMemory 错误的各种情况。

症状还是根本原因?

为了找到原因,异常的文本在末尾包含了详细的消息。让我们检查所有的错误。

错误 1 – Java堆空间:

由于过度使用终结器的应用程序会出现此错误。如果一个类有一个 finalize 方法,那么该类型的对象在垃圾回收时不会回收它们的空间。相反,在垃圾回收之后,对象会排队等待最终确定,这将在稍后发生。

执行:

  • 终结器由服务终结队列的守护线程执行。
  • 如果终结器线程跟不上终结队列, Java堆可能会填满,并且会抛出这种类型的 OutOfMemoryError 异常。
  • 问题也可以像配置问题一样简单,其中指定的堆大小(或默认大小,如果未指定)对于应用程序来说是不够的。
Java
// Java program to illustrate
// Heap error
 
import java.util.*;
 
public class Heap {
    static List list = new ArrayList();
 
    public static void main(String args[]) throws Exception
    {
        Integer[] array = new Integer[10000 * 10000];
    }
}


Java
// Java program to illustrate
// GC Overhead limit exceeded
 
import java.util.*;
 
public class Wrapper {
    public static void main(String args[]) throws Exception
    {
        Map m = new HashMap();
        m = System.getProperties();
        Random r = new Random();
       
        while (true) {
            m.put(r.nextInt(), "randomValue");
        }
    }
}


Java
// Java program to illustrate
// Permgen Space error
 
import javassist.ClassPool;
 
public class Permgen {
    static ClassPool classPool = ClassPool.getDefault();
 
    public static void main(String args[]) throws Exception
    {
        for (int i = 0; i < 1000000000; i++) {
            Class c = classPool.makeClass("com.saket.demo.Permgen" + i).toClass();
            System.out.println(c.getName());
        }
    }
}


Java
// Java program to illustrate
// Metaspace error
 
import java.util.*;
 
public class Metaspace {
    static javassist.ClassPool cp
        = javassist.ClassPool.getDefault();
 
    public static void main(String args[]) throws Exception
    {
        for (int i = 0; i < 100000; i++) {
            Class c = cp.makeClass(
                            "com.saket.demo.Metaspace" + i)
                          .toClass();
        }
    }
}


Java
// Java program to illustrate
// Requested array size
// exceeds VM limit error
 
import java.util.*;
 
public class GFG {
    static List list = new ArrayList();
 
    public static void main(String args[]) throws Exception
    {
        Integer[] array = new Integer[10000 * 10000];
    }
}


Java
// Java program to illustrate
// new native thread error
 
import java.util.*;
 
public class GFG {
    public static void main(String args[]) throws Exception
    {
        while (true) {
            new Thread(new Runnable() {
                public void run()
                {
                    try {
                        Thread.sleep(1000000000);
                    }
                    catch (InterruptedException e) {
                    }
                }
            }).start();
        }
    }
}


输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at Heap.main(Heap.java:11)

当您执行上面的代码时,您可能希望它永远运行而不会出现任何问题。结果,随着时间的推移,随着泄漏代码的不断使用,“缓存”的结果最终会消耗大量的Java堆空间,并且当泄漏的内存填满堆区域中的所有可用内存时,垃圾收集无法要清理它,会抛出Java.lang.OutOfMemoryError: Java heap space

预防:在“监控待定对象”中查看如何监控待定对象。

错误 2 – 超出 GC 开销限制:

此错误表明垃圾收集器一直在运行, Java程序进展缓慢。在垃圾回收之后,如果Java进程花费大约 98% 以上的时间进行垃圾回收,并且如果它回收的堆空间少于 2%,并且到目前为止一直在执行最后 5 个(编译时间常数)连续垃圾回收,然后抛出Java.lang.OutOfMemoryError

通常会抛出此异常,因为实时数据量几乎无法放入Java堆中,几乎没有用于新分配的可用空间。

Java

// Java program to illustrate
// GC Overhead limit exceeded
 
import java.util.*;
 
public class Wrapper {
    public static void main(String args[]) throws Exception
    {
        Map m = new HashMap();
        m = System.getProperties();
        Random r = new Random();
       
        while (true) {
            m.put(r.nextInt(), "randomValue");
        }
    }
}

如果你用Java -Xmx100m -XX:+UseParallelGC Wrapper运行这个程序,那么输出将是这样的:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.Integer.valueOf(Integer.java:832)
    at Wrapper.main(error.java:9)

预防措施:增加堆大小并使用命令行标志-XX:-UseGCOverheadLimit 将其关闭。

错误 3 – Permgen 空间被抛出:

Java内存被分成不同的区域。所有这些区域的大小,包括 permgen 区域,都是在 JVM 启动期间设置的。如果您不自己设置大小,将使用特定于平台的默认值。

Java.lang.OutOfMemoryError : PermGen space 错误表示永久代在内存中的区域已用尽。

Java

// Java program to illustrate
// Permgen Space error
 
import javassist.ClassPool;
 
public class Permgen {
    static ClassPool classPool = ClassPool.getDefault();
 
    public static void main(String args[]) throws Exception
    {
        for (int i = 0; i < 1000000000; i++) {
            Class c = classPool.makeClass("com.saket.demo.Permgen" + i).toClass();
            System.out.println(c.getName());
        }
    }
}

在上面的示例代码中,代码迭代循环并在运行时生成类。 Javassist 库正在处理类生成的复杂性。

运行上述代码将继续生成新类并将它们的定义加载到 Permgen 空间中,直到空间被充分利用并抛出Java.lang.OutOfMemoryError : Permgen 空间。

预防:当应用启动时PermGen耗尽导致OutOfMemoryError,解决方法很简单。应用程序只是需要更多空间来将所有类加载到 PermGen 区域,因此我们需要增加它的大小。为此,请更改您的应用程序启动配置并添加(或增加) -XX:MaxPermSize参数,类似于以下示例:

java -XX:MaxPermSize=512m com.saket.demo.Permgen

错误 4 – 元空间:

Java类元数据在本机内存中分配。假设类元数据的元空间已用尽,则抛出带有详细 MetaSpace 的Java.lang.OutOfMemoryError异常。

用于类元数据的元空间量受参数 MaxMetaSpaceSize 限制,该参数在命令行中指定。当类元数据所需的本机内存量超过 MaxMetaSpaceSize 时,将引发带有详细 MetaSpace 的Java.lang.OutOfMemoryError 异常。

Java

// Java program to illustrate
// Metaspace error
 
import java.util.*;
 
public class Metaspace {
    static javassist.ClassPool cp
        = javassist.ClassPool.getDefault();
 
    public static void main(String args[]) throws Exception
    {
        for (int i = 0; i < 100000; i++) {
            Class c = cp.makeClass(
                            "com.saket.demo.Metaspace" + i)
                          .toClass();
        }
    }
}

此代码将继续生成新类并将其定义加载到 Metaspace,直到空间被充分利用并抛出Java.lang.OutOfMemoryError: Metaspace。当使用 -XX:MaxMetaspaceSize=64m 启动时,然后在 Mac OS X 上,我的Java 1.8.0_05 在加载了大约 70、000 个类时死掉了。

预防:如果MaxMetaSpaceSize ,已经在命令行设置,增加它的值。 MetaSpace 是从与Java堆相同的地址空间分配的。减小Java堆的大小将为 MetaSpace 提供更多空间。如果Java堆中的可用空间过多,这只是一个正确的权衡。

错误 5 – 请求的阵列大小超出 VM 限制:

此错误表明应用程序试图分配一个大于堆大小的数组。例如,如果应用程序尝试分配 1024 MB 的数组,但最大堆大小为 512 MB,则将抛出 OutOfMemoryError并显示“请求的数组大小超出 VM 限制”。

Java

// Java program to illustrate
// Requested array size
// exceeds VM limit error
 
import java.util.*;
 
public class GFG {
    static List list = new ArrayList();
 
    public static void main(String args[]) throws Exception
    {
        Integer[] array = new Integer[10000 * 10000];
    }
}

输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at GFG.main(GFG.java:12)

Java.lang.OutOfMemoryError: Requested array size exceeded VM limit 可能由于以下任一情况而出现:

  • 您的数组变得太大,最终的大小在平台限制和 Integer.MAX_INT 之间
  • 您故意尝试分配大于 2^31-1 个元素的数组来试验限制。

错误 6 – 请求大小字节是有原因的。交换空间不足?:

当从本机堆分配失败并且本机堆可能接近耗尽时,就会发生这种明显的异常。错误指示失败请求的大小(以字节为单位)以及内存请求的原因。通常,原因是报告分配失败的源模块的名称,尽管有时它是实际原因。

Java.lang.OutOfMemoryError: Out of swap space错误通常是由操作系统级别的问题引起的,例如:

  • 操作系统配置的交换空间不足。
  • 系统上的另一个进程正在消耗所有内存资源。

预防:当抛出此错误消息时,VM 会调用致命错误处理机制(即,它会生成一个致命错误日志文件,其中包含有关崩溃时线程、进程和系统的有用信息)。在本机堆耗尽的情况下,日志中的堆内存和内存映射信息会很有用。

错误 7 – 原因 stack_trace_with_native_method:

每当抛出此错误消息(原因 stack_trace_with_native_method)时,就会打印堆栈跟踪,其中顶部帧是本机方法,这表明本机方法遇到分配失败。此消息与上一条消息之间的区别在于,分配失败是在Java本机接口 (JNI) 或本机方法中检测到的,而不是在 JVM 代码中检测到的。

Java

// Java program to illustrate
// new native thread error
 
import java.util.*;
 
public class GFG {
    public static void main(String args[]) throws Exception
    {
        while (true) {
            new Thread(new Runnable() {
                public void run()
                {
                    try {
                        Thread.sleep(1000000000);
                    }
                    catch (InterruptedException e) {
                    }
                }
            }).start();
        }
    }
}

确切的本机线程限制取决于平台。例如,测试 Mac OS X 显示: 64 位 Mac OS X 10.9, Java 1.7.0_45 – JVM 在创建 #2031 线程后死亡

预防:使用操作系统的本机实用程序进一步诊断问题。有关可用于各种操作系统的工具的更多信息,请参阅本机操作系统工具。