了解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 线程后死亡
预防:使用操作系统的本机实用程序进一步诊断问题。有关可用于各种操作系统的工具的更多信息,请参阅本机操作系统工具。