OOM异常会导致JVM退出吗?

本文深入探讨了Java中OutOfMemoryError(OOM)错误的触发机制及其对Java虚拟机(JVM)的影响。揭示了线程OOM时,JVM如何通过异常处理机制维持运行,以及在何种条件下可能导致JVM退出。

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

最近发现bi项目,5月6号有OOM错误,并且有dump文件生成,奇怪的是tomcat没有挂掉。
即使发生了oom,程序也不一定会挂?jvm回收成功就可以。

一、问题来源
一次生产事故,由于一次性从数据库查询过多数据导致***线程*** OOM:Java heap space 异常(千万级表,JVM堆内存2G),但是在线程OOM发生时,java进程却没有立即挂掉。
不符合所谓发生OOM,程序就会挂的“预期”,因此进行深入了解。

二、OOM与异常
说到底OutOfMemoryError也只是一个java中的异常而已,属于Error一系非检查异常:
Object
    Throwable
        Error
            VirtualMachineError
                OutOfMemoryError
堆内存不够与异常的关系
线程发生OOM Java heap space,首先是堆空间不够了,然后再由jvm在申请分配空间的方法调用上抛出OOM异常。
对于线程,它会像处理普通异常一样,处理OutOfMemoryError。

三、异常与线程
线程是资源调度的基本单位,Java在设计线程时充分考虑了线程的独立性。在异常方面,保持了线程异常的独立性,在线程执行中发生的异常,都由线程自身解决,不会抛出到执行它的线程。

在线程的实现上,也保证了这种独立性。

检查异常不可抛
线程的实现java.lang.Runnable实现了java.lang.Runnable接口,线程通过其run方法运行,方法签名如下:
public abstract void run();
由java语法保证了实现Runnable接口或者继承Thread类的子类,其run方法也不能声明抛出任何检查异常(checked exception)。因此在线程方法执行中发生的任何检查异常,必须在线程中处理。

默认异常处理器

除了检查异常,java中还有非检查异常(unchecked exception),这种异常无需显式声明也能沿着方法调用链向上抛出。线程对于这种未处理的异常,提供了默认异常处理器:

/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM. (将未被捕获的异常分发给处理器。这个方法只被JVM调用)
*/
private void dispatchUncaughtException(Throwable e) {
   getUncaughtExceptionHandler().uncaughtException(this, e);
}

Thread的init()方法线程至少有一个默认异常处理器,兜底的异常处理器是当前线程父线程的线程组ThreadGroup,可以看到线程组是有能力处理异常的:
public
class ThreadGroup implements Thread.UncaughtExceptionHandler {}
线程通过这两种机制,保证内部发生的异常,在线程内解决,而不会抛出给启动线程的外部线程。四、JVM退出条件
java虚拟机退出的条件是:虚拟机内不存在非守护线程。
线程发生未处理的异常(未处理异常由默认异常处理器处理)会导致线程结束,而与JVM的退出毫无关系。
OOM也是一种异常,它的发生也不会导致JVM退出。以下实例说明:

static class OOMObject {
}

// 为快速发生oom,设置堆大小; VM args: -Xms20m -Xmx20m 
public static void main(String[] args) throws InterruptedException {
     new Thread(() -> {
         List<OOMObject> list = new ArrayList<>();
         while (true) {
             list.add(new OOMObject());
         }
     }).start();

     while (true) {
         System.out.println(Thread.currentThread().getName() + " continuing...");
         Thread.sleep(1000L);
     }
 }

线程抛出java.lang.OutOfMemoryError: Java heap space后,main线程依旧会循环打印main continuing...。
线程中发生OOM异常如此,发生其他异常也如此,不影响其他线程,也不会导致JVM退出。

五、OOM与JVM退出
OOM的发生表示了此刻JVM堆内存告罄,不能分配出更多的资源,或者gc回收效率不可观。一个线程的OOM,在一定程度的并发下,若此时其他线程也需要申请堆内存,那么其他线程也会因为申请不到内存而OOM,甚至连锁反应导致整个JVM的退出。

以上示例没有导致JVM退出的原因在于,线程通过往局部变量集合中不断加入对象,产生OOM。线程因异常退出后,集合中的对象由于引用不可达,会被gc,这样就有了足够的堆内存供其他线程使用。

若示例中的list是一个“全局”的类static变量,那么即使线程退出,内存也得不到释放。这时其他线程如果不断再申请堆内存资源,就会造成连锁反应导致JVM退出。

原文:https://blog.csdn.net/SakuraInLuoJia/article/details/89502822 

<think>我们之前讨论了Java的内存管理,特别是与C++的区别。现在问题聚焦于为什么Java的OutOfMemoryError不会导致JVM崩溃。 原因在于JVM的设计:当发生OutOfMemoryError时,它只是一个被抛出的异常JVM会捕获并处理它,允许程序有机会恢复或优雅地关闭,而不是立即崩溃。 下面详细解释:</think>### 为什么Java的`OutOfMemoryError`不会导致JVM崩溃? #### **核心机制:JVM的错误处理架构** Java通过**分层错误处理机制**保证内存错误不会直接崩溃JVM: ```text ┌──────────────────────┐ │ JVM进程 │ ├──────────────────────┤ │ ┌─────────────────┐ │ │ │ 内存分配请求 │◄─尝试分配对象/数组 │ └────────┬────────┘ │ │ ┌────────▼────────┐ │ │ │ 内存不足检测机制 │◄─1. 堆空间不足 2. 本地内存不足 │ └────────┬────────┘ │ │ ┌────────▼────────┐ │ │ │ 抛出OutOfMemory │◄─创建Error对象而非崩溃 │ │ Error │ │ │ └────────┬────────┘ │ │ ┌────────▼────────┐ │ │ │ 错误处理流程 │◄─3. 查找catch块 4. 执行finally │ └────────┬────────┘ │ │ ┌────────▼────────┐ │ │ │ 继续运行或退出 │◄─5. 未捕获则线程终止,JVM仍运行 │ └─────────────────┘ │ └──────────────────────┘ ``` #### **关键设计原理** 1. **Error的可捕获性** `OutOfMemoryError`继承自`Throwable`,可通过`try-catch`捕获: ```java try { // 尝试分配超大数组 int[] arr = new int[Integer.MAX_VALUE]; } catch (OutOfMemoryError e) { System.err.println("内存不足! 执行清理操作..."); // 释放资源或降级处理 } finally { System.out.println("资源清理完成"); } ``` 2. **线程隔离机制** - 错误仅终止**当前线程**而非整个JVM - 守护线程终止不影响主线程 ```java new Thread(() -> { try { // 在子线程触发OOM long[] leak = new long[20_000_000_000]; } catch (OutOfMemoryError e) { System.out.println("子线程内存溢出"); } }).start(); // 主线程继续运行 System.out.println("主线程正常运行"); ``` 3. **GC的弹性恢复** 内存不足时JVM尝试: - 触发Full GC回收内存 - 若回收失败才抛出Error ```java // JVM参数: -XX:+PrintGCDetails List<Object> list = new ArrayList<>(); while (true) { list.add(new byte[10_000_000]); // 每次分配10MB // 观察GC日志:Full GC尝试回收 } ``` 4. **本地内存保护** 通过**安全点(Safepoint)** 机制确保: - 本地内存溢出时JVM能安全挂起线程 - 避免操作系统直接终止进程 #### **与C++段错误的对比** | 场景 | C++ | Java | |---------------------|----------------------|-----------------------| | **堆内存耗尽** | 程序崩溃 | 抛出OutOfMemoryError | | **栈溢出** | 栈溢出崩溃 | 抛出StackOverflowError| | **空指针访问** | 段错误(Segfault) | 抛出NullPointerException| | **错误处理** | 无标准恢复机制 | try-catch可捕获 | #### **实际应用:优雅降级方案** ```java public class OOMHandler { private static final int MAX_RETRY = 3; public void processBigData() { int attempt = 0; while (attempt < MAX_RETRY) { try { byte[] data = loadHugeDataset(); // 可能OOM process(data); return; } catch (OutOfMemoryError e) { attempt++; System.gc(); // 建议GC reduceMemoryFootprint(); // 释放缓存等 } } fallbackToDiskProcessing(); // 降级到磁盘处理 } private byte[] loadHugeDataset() { return new byte[1024 * 1024 * 1024]; // 1GB } // 其他方法省略... } ``` #### **根本原因总结** 1. **托管环境设计**:JVM作为"安全容器"隔离操作系统 2. **异常机制**:内存错误转化为可捕获对象 3. **线程沙箱**:错误线程不影响其他线程 4. **GC安全网**:内存耗尽前主动回收尝试 5. **JIT保护**:即时编译器生成安全检查代码 > **注意**:虽然JVM不会崩溃,但未处理的`OutOfMemoryError`会导致线程终止,持续内存泄漏最终会使JVM无可用线程而挂起。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值