简述 OutOfMemoryError

本文详细探讨Java OutOfMemoryError的成因,涵盖对象创建、内存区域限制、GC机制等方面,并提供解决策略。了解内存溢出的原因有助于提升代码健壮性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在日常开发中,我们多多少少都会遇到 OutOfMemoryError(内存溢出)的 Error 异常,简单来说就是内存不够用了。可是 OutOfMemoryError 具体是怎么一回事?什么原因会导致这个异常的发生?

如何加载 OutOfMemoryError

OutOfMemoryError 在 JVM 启动的时候就已经被加载,可以通过下面这个命令,打印出虚拟机装入的类的信息。

java -verbose:class -version
图1
图1

根据图1的结果,JVM 启动过程中加载了 OutOfMemoryError 类。同时,创建了几个 OutOfMemoryError 对象,每个 OutOfMemoryError 对象代表了一种内存溢出的场景,例如:

java.lang.OutOfMemoryError: Java heap space

◉ 创建新的对象时, 堆内存中的空间不足以存放新创建的对象

◉ 堆内存使用量达到最大内存限制

java.lang.OutOfMemoryError: Metaspace

◉ Metaspace(元数据区)已被占满,主要原因是加载到内存中的 class 数量太多或者体积太大。

java.lang.OutOfMemoryError: GC overhead limit exceeded

◉ 连续多次 GC 回收效率太低,默认情况下, 如果GC花费的时间超过98%, 并且 GC 回收的内存少于2%,JVM就会抛出这个错误。

java.lang.OutOfMemoryError: Out of swap space

◉ 虚拟内存不足。

java.lang.OutOfMemoryError: Unable to create new native thread

◉ 操作系统的虚拟内存已耗尽,或者线程数超过了操作系统的限制,因此操作系统创建新的 native thread 失败。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

◉ 创建的数组长度超过限制。

OutOfMemoryError 的数量

根据 PreallocatedOutOfMemoryErrorCount 这个参数可以设定创建 OutOfMemoryError 的数量。不过,参数只能在 debug 版本可以被设置,通常我们使用的 release 版本不能自定义设置。

PreallocatedOutOfMemoryErrorCount 的默认值为4,具体来源可以参考以下网址。

https://stas-blogspot.blogspot.com/2011/07/most-complete-list-of-xx-options-for.html

从 universe_post_init() 方法可以看到,OutOfMemoryError 类在 JVM 启动的时候就根据 PreallocatedOutOfMemoryErrorCount 参数加载固定数量的 OutOfMemoryError 类。

// 在JVM主要组件都初始化完成后,编译器调用的方法
bool universe_post_init() {
  // ……
  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  // ……
}

怎么抛出 OutOfMemoryError

抛出 OutOfMemoryError 异常,必然是发生在内存分配的时候,例如创建对象的时候需要分配内存空间,新对象存放在 Eden区。

抛出异常的具体流程如下:

如果 Eden区 内存空间不足,或者对象比较大以至于老年区也分配失败,那么就会触发 Young GC。

如果 Young GC 后 Eden区 仍旧没有足够的内存空间进行分配,那么就会触发 Old GC。

如果 Full GC 后 Eden区 和 老年区 仍旧没有足够的内存空间进行分配,那么就会抛出 OutOfMemoryError 异常。

具体流程如下图所示:

图2
图2

再说几句

OutOfMemoryError 就是一个比较常见的异常,然而,往往这么常见的异常我们只知其然,而不知其所以然。永远保持一颗探索的好奇心,你将发现世界原来是这么深邃。

### Java中内存溢出与内存泄漏的概念及区别 #### 内存溢出(OutOfMemoryError) 内存溢出是指程序运行时需要的内存超出了当前可用的内存资源。这种情况下,JVM无法为新对象分配足够的内存空间,从而抛出 `OutOfMemoryError` 异常[^2]。内存溢出通常由以下原因引起: - 堆内存不足:当尝试在堆中分配对象时,如果堆内存已满且无法扩展,则会触发 `java.lang.OutOfMemoryError: Java heap space`[^3]。 - 元空间不足:在加载大量类或使用反射生成类时,元空间可能耗尽,导致 `java.lang.OutOfMemoryError: Metaspace`。 - 线程数过多:操作系统对线程数量有限制,如果创建的线程数超过限制,将引发 `java.lang.OutOfMemoryError: Unable to create new native thread`[^3]。 #### 内存泄漏(Memory Leak) 内存泄漏是指程序运行过程中,已经不再使用的对象仍然占用着内存空间,而这些内存空间无法被垃圾回收器回收。这种情况会导致可用内存逐渐减少,最终可能导致内存溢出[^1]。内存泄漏的主要原因包括: - 静态集合类持有对象引用:例如静态的 `List` 或 `Map` 持有大量对象引用,导致这些对象无法被垃圾回收[^4]。 - 未关闭的资源:如数据库连接、文件流或网络套接字未正确关闭,导致资源和内存持续占用。 - 内部类对外部类的引用:内部类持有了外部类的隐式引用,导致外部类对象无法被回收[^4]。 #### 区别 | **特性** | **内存溢出** | **内存泄漏** | |------------------|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| | **定义** | 程序运行时所需的内存超出当前可用内存,导致 JVM 抛出异常[^2]。 | 程序运行时,不再使用的对象仍占用内存,导致内存无法被释放[^1]。 | | **触发条件** | 堆内存不足、元空间不足或线程数过多等。 | 静态集合类持有对象引用、未关闭资源或内部类引用外部类等[^4]。 | | **表现形式** | 直接抛出 `OutOfMemoryError` 异常,程序中断[^2]。 | 内存逐渐消耗殆尽,最终可能导致内存溢出[^1]。 | | **解决方法** | 增加 JVM 堆内存配置(如 `-Xmx` 参数)、优化代码逻辑以减少内存使用。 | 使用内存分析工具(如 VisualVM 或 Eclipse Memory Analyzer)检测泄漏点并修复。 | #### 示例代码 以下是一个内存泄漏的示例代码: ```java import java.util.ArrayList; import java.util.List; public class MemoryLeakExample { private static List<byte[]> list = new ArrayList<>(); public static void main(String[] args) { while (true) { list.add(new byte[1024 * 1024]); // 不断向列表中添加大对象 try { Thread.sleep(10); // 模拟延迟 } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 上述代码中,静态 `List` 持有大量字节数组对象,导致这些对象无法被垃圾回收,从而引发内存泄漏。 #### 排查工具 可以使用以下工具来检测内存溢出和内存泄漏问题: - **VisualVM**:用于监控 JVM 的内存使用情况,并生成堆转储文件进行分析。 - **Eclipse Memory Analyzer**:分析堆转储文件,定位内存泄漏的根本原因。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值