理解 Java 中的 OutOfMemoryError 异常

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

线程“main”中的异常 java.lang.OutOfMemoryError: Java heap space

通常,当 Java 虚拟机由于内存不足而无法分配对象并且垃圾收集器无法提供更多内存时,会抛出此错误。

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

 

java.lang.OutOfMemoryError例外,也可以通过本机库的代码时,抛出本地分配不能满足(例如,如果交换空间不足)。让我们了解可能发生 OutOfMemory 错误的各种情况。

 

症状还是根本原因?

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

  1. 错误 1 ​​– Java 堆空间:由于过度使用终结器的应用程序而出现此错误。如果类具有 finalize 方法,则该类型的对象不会在垃圾收集时回收其空间。取而代之的是,在垃圾回收之后,对象将排队等待完成,这将在稍后的时间发生。执行:
    • 终结器由服务终结队列的守护线程执行。
    • 如果终结器线程跟不上终结队列,那么 Java 堆可能会填满,并且会抛出这种类型的 OutOfMemoryError 异常。
    • 该问题也可以像配置问题一样简单,其中指定的堆大小(或默认大小,如果未指定)对于应用程序来说是不够的。
// Java program to illustrate
// Heap error
import java.util.*;

public class Heap {
	static List<String> list = new ArrayList<String>();

public static void main(String args[]) throws Exception
	{
		Integer[] array = new Integer[10000 * 10000];
	}
}
  1. 当您执行上面的代码时,您可能希望它永远运行而不会出现任何问题。结果,随着时间的推移,随着泄漏代码的不断使用,“缓存”的结果最终会消耗大量 Java 堆空间,并且当泄漏的内存填满堆区域中的所有可用内存并且垃圾收集无法清理它,java.lang.OutOfMemoryError:Java 堆空间被抛出。预防:检查如何在监视待完成的对象监视待完成的对象。
  2. 错误 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");
		}
	}
}

 

  1. 如果你用java -Xmx100m -XX:+UseParallelGC Wrapper运行这个程序,那么输出将是这样的:
    线程“main”中的异常 java.lang.OutOfMemoryError:超出 GC 开销限制
        在 java.lang.Integer.valueOf(Integer.java:832)
        在 Wrapper.main(error.java:9)
    

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

  2. 错误 3 – Permgen space is throws : Java 内存被分成不同的区域。所有这些区域的大小,包括 permgen 区域,都是在 JVM 启动期间设置的。如果您不自己设置大小,将使用特定于平台的默认值。
    java.lang.OutOfMemoryError:PermGen space的错误表明持久代的内存区域被耗尽。
// 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 space 被抛出。
预防:当应用程序启动时由于PermGen耗尽导致OutOfMemoryError时,解决方法很简单。应用程序只需要更多空间将所有类加载到 PermGen 区域,所以我们只需要增加它的大小。为此,请更改您的应用程序启动配置并添加(或增加如果存在)-XX:MaxPermSize 类似于以下示例的参数:

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

 

错误 4 – 空间: Java 类元数据在本机内存中分配。如果类元数据的元空间耗尽,则会抛出带有详细信息元空间的java.lang.OutOfMemoryError异常。
可用于类元数据的元空间量受命令行上指定的参数 MaxMetaSpaceSize 的限制。当类元数据所需的本机内存量超过 MaxMetaSpaceSize 时,将引发带有详细信息 MetaSpace 的 java.lang.OutOfMemoryError 异常。

// 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();
		}
	}
}

 

  1. 此代码将不断生成新类并将它们的定义加载到元空间,直到空间被完全利用并且 java.lang.OutOfMemoryError: Metaspace 被抛出。当使用 -XX:MaxMetaspaceSize=64m 启动时,然后在 Mac OS X 上,我的 Java 1.8.0_05 在加载了大约 70, 000 个类时死亡。预防:如果MaxMetaSpaceSize已在命令行上设置,则增加其值。MetaSpace 是从与 Java 堆相同的地址空间分配的。减少 Java 堆的大小将为 MetaSpace 提供更多空间。如果 Java 堆中有多余的可用空间,这只是一个正确的权衡。
  2. 错误 5 – 请求的数组大小超出 VM 限制:此错误表示应用程序尝试分配大于堆大小的数组。例如,如果应用程序尝试分配 1024 MB 的数组,但最大堆大小为 512 MB,则将抛出 OutOfMemoryError并显示“请求的数组大小超出 VM 限制”。
// Java program to illustrate
// Requested array size
// exceeds VM limit error
import java.util.*;

public class GFG {
	static List<String> list = new ArrayList<String>();

public static void main(String args[]) throws Exception
	{
		Integer[] array = new Integer[10000 * 10000];
	}
}

 

  1. java.lang.OutOfMemoryError: 请求的数组大小超出 VM 限制可能是以下任一情况的结果:
    • 您的数组变得太大,最终大小介于平台限制和 Integer.MAX_INT 之间
    • 您故意尝试分配大于 2^31-1 元素的数组来试验限制。
  2. 错误 6 – 请求大小字节的原因。交换空间不足?:当来自本机堆的分配失败并且本机堆可能接近耗尽时,会发生这种明显的异常。该错误指示失败的请求的大小(以字节为单位)以及内存请求的原因。通常原因是报告分配失败的源模块的名称,尽管有时它是实际原因。
    java.lang.OutOfMemoryError: Out of swap space错误通常是由操作系统级别的问题引起的,例如:

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

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

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

 

  1. 确切的本机线程限制取决于平台,例如测试 Mac OS X 显示:64 位 Mac OS X 10.9、Java 1.7.0_45 – JVM 在 #2031 线程创建后终止预防:使用操作系统的本机实用程序进一步诊断问题。有关可用于各种操作系统的工具的更多信息,请参阅本机操作系统工具

 

发表评论