李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 系列文章
  • AI工具集
  • 论坛
  • 左邻右舍
    • 左邻右舍
    • 朋友圈
    • 游客中心
  • 留言
    • 留言
    • 走心评论
  • 关于
    • 关于本站
    • Now
    • 每日心情
    • 网站地图
    • 网站统计
    • 赞助
  • 星球穿梭
  • 🚇开往
白雪成红
惟坚韧者始能遂其志
  1. 首页
  2. 原创
  3. 技术
  4. 正文

OOM后,JVM一定会退出吗?为什么?

2024年4月15日 约 1,961 字7 分钟 164 0 0
本文最后更新于 2024年4月15日,距今已 908 天,其中的信息可能已经发生变化,请注意甄别。

问题背景

问题是,咱们常常说:发生OOM,程序就会挂。

很多情况是:发生OOM了,JVM没有挂。

回顾一下OOM与异常

来看一下 OutOfMemoryError,说到底,OutOfMemoryError 也只是一个java中的异常而已,

OutOfMemoryError 属于Error一系非检查异常, 其继承关系如下

Object
 Throwable
  Error
   VirtualMachineError
    OutOfMemoryError

再来看看,堆内存不够与 OutOfMemoryError 异常的关系

线程发生OutOfMemoryError,首先是堆空间不够了,然后再由jvm在申请分配空间的的时候,在调用上抛出OOM异常。

申请内存的线程,会会像处理普通的其他异常一样,处理OutOfMemoryError。

线程是资源调度的基本单位,Java在设计线程时充分考虑了线程的独立性。

在异常方面,线程也保持了线程异常的独立性。

在线程执行中,如果发生的异常,都由线程进行独立的处理,而不是也不会抛出到其它的线程。这就是保证了这种线程的独立性。

从线程的实现维度,也可以看到异常处理的策略。

线程Thread里边,最终会执行内部target对象的run方法,也就是java.lang.Runnable接口实现方法,线程通过其run方法运行,方法签名如下:

public abstract void run();

注意这个方法,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 不存在非守护线程(前台线程),JVM就会退出。

线程发生未处理的异常(未处理异常由默认异常处理器处理)会导致线程结束,而线程结束了, 如果还有非守护线程(前台线程),JVM也不会退出。

OOM也是一种异常,它的发生也不会导致JVM退出。

所以,OOM 与JVM的退出,没有很强的关系。

以下实例说明:

  • 实例一:线程OOM,JVM不一定退出
  • 实例二:线程池OOM,JVM不一定退出

实例一:线程OOM,JVM不一定退出

thread-0 线程抛出OOM 后线程结束后,main线程依旧会循环打印"我还行....."。

线程中发生OOM异常,和发生其他异常一样,只是那个线程终止了,但是不影响其他线程,

thread-0 线程线程OOM,也不会导致JVM退出。

实例二:线程池OOM,JVM不一定退出

class OOMThreadPool {
    private final Byte[] toLeak;

    public OOMThreadPool() {
        toLeak = new Byte[1024 * 1024];
    }

    static final Thread[] t = new Thread[1];
    static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 5,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(9),

            new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    t[0] = new Thread(r);
                    t[0].setDaemon(false);
                    t[0].setPriority(Thread.NORM_PRIORITY);
                    t[0].setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                        @Override
                        public void uncaughtException(Thread t, Throwable e) {
                            e.printStackTrace();
                            System.out.println(t.getName() + " 的状态:" + t.getState());
                            System.out.println("这里是没有捕获的处理 ====> " + t.getId() + "==> " + e.getLocalizedMessage());
                        }
                    });
                    return t[0];
                }
            },
            new ThreadPoolExecutor.DiscardOldestPolicy()) {

        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            System.out.println(Thread.currentThread().getName() + " 任务执行完成,但是线程不会结束");
            if (null != t) {
                System.out.println(Thread.currentThread().getName() + "任务异常了");
                t.printStackTrace();

            }

        }
    };

    // 为快速发生oom,设置堆大小; VM args: -Xms10m -Xmx10m
    public static void main(String[] args) throws InterruptedException {
        List<OOMThreadPool> list = new LinkedList<>();

        Runnable target = () -> {
            System.out.println(Thread.currentThread().getName() + " 开始了");
            try {
                while (true) {
                    list.add(new OOMThreadPool());
                }
            }catch ( Throwable throwable)
            {
                throwable.printStackTrace();
            }

        };

        threadPool.submit(target);
        while (true) {
            System.out.println(Thread.currentThread().getName() + " 我还行...");
            System.out.println(t[0].getName() + " 的状态:" + t[0].getState());
            Thread.sleep(1000L);
        }
    }
}

在线程池中,thread-0 线程抛出OOM 后线程结束后,main线程依旧会循环打印"我还行....."。

在线程池中,thread-0 线程 的任务结束了, 但是 线程没有结束,还是可以执行新任务的。

OOM与JVM退出的关系

什么时候发生OOM、JVM才退出呢?

  • 场景1:所有的非守护线程由于申请不到内存而OOM,所有非守护线程退出,JVM退出,这个属于主动退出

OOM的发生表示了此刻JVM堆内存告罄,不能分配出更多的资源,或者GC回收效率不可观。

一个线程的OOM,在一定程度的并发下,若此时其他线程(含非守护线程)也需要申请堆内存,那么其他线程也会因为申请不到内存而OOM,甚至连锁反应导致整个JVM的退出。

  • 场景2:OOM溢出,说明内存耗尽,如果操作系统内存耗尽,就会发生OOM killer(Out Of Memory killer),干掉JVM进程,导致被动退出

Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(),挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。

补充

OOM可以catch吗?

可以,一切Throwable都可以catch

byte[] b = null;
try{
    b= new byte[1024 * 10240 * 1];
}catch (Error e){
    System.out.println(e.getMessage()+" "+b);
}

这段代码中,发生oom后,对象创建失败,会打印如下

Java heap space null

线程与异常的关系

  1. 线程不允许往外抛受检异常
    从方法签名public abstract void run();就可以看出
  2. 对于非受检异常,或是未能捕获的受检异常,会交给UncaughtExceptionHandler处理,具体过程如下
    当异常出现后,虚拟机调用
private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

一个线程为主动设置UncaughtExceptionHandler时,会由当前线程的group处理,group在init方法中初始化,它是ThreadGroup对象,有能力处理未捕获异常
class ThreadGroup implements Thread.UncaughtExceptionHandler

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

从上面可以看出,它对异常的处理就是打印异常信息,结束后线程也就退出了

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
分享到

OOM后,JVM一定会退出吗?为什么?

也可使用浏览器菜单中的「分享」功能

微信扫一扫分享

标签: JAVA JVM OOM
最后更新:2024年4月15日

岁月同一天 10 月 11 日

回望过去的今天,你在写什么

  • 中间件
    1 年前 2025年10月11日
    Kafka 为什么要抛弃 Zookeeper?

    Kafka 抛弃 Zookeeper(引入 KRaft 模式)是为了解决长期依赖 Zookeeper 带来的性能瓶颈、架…

  • 中间件
    1 年前 2025年10月11日
    高性能场景为什么推荐使用PostgreSQL,而非MySQL?

    在高性能场景(如高并发读写、复杂查询、大数据量存储、多维度数据处理等)中,PostgreSQL 常被优先推荐,核心原因在…

  • 原创
    7 年前 2019年10月11日
    JAVA关键字之volatile关键字说明

    volatile作用 volatile主要是为了解决多线程内存不可见问题。 对于一写多读,是可以解决变量同步问题,但是如…

相关文章
  • jmap命令(jdk1.8)2020年9月27日
  • Eclipse反编译插件Jad安装2019年9月27日
  • Java程序基本结构2022年7月15日
  • 解决Intellij IDEA运行报Command line is too long的问题2021年2月18日
  • 重构 Controller 终极指南:从臃肿到优雅的 7 大黄金法则 + 实战技巧2025年10月31日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

还没有评论,快来抢沙发吧~

三人行,必有我师焉。择其善者而从之,其不善者而改之。

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
讨论下XXL-JOB GLUE模式的安全问题 心气没了,干什么都不得劲 Kratos-plus v1.1.26版本更新说明 我觉得是时候公布我的导航网站了 代码是 AI 写的,生产事故谁背锅? 给主题增加了游客中心功能
给主题增加了游客中心功能我觉得是时候公布我的导航网站了游本昌去世关于主题加载速度优化的一点儿小演进给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能心气没了,干什么都不得劲
深度解析 Kafka Rebalance:从原理到实战,彻底解决消息积压、重复与丢失 分代ZGC这么牛?底层原理是什么? 离骚 数据库事务的隔离级别 Java中PO、VO、BO、DTO、POJO、DAO释义 【收藏】Java问题排查相关工具工具箱
最近评论
老张博客 发布于 14 小时前(10月10日) NAS现在就不要搞了吧,除非你手里有内存和硬盘。
李锋镝 发布于 20 小时前(10月10日) 确实,很多不必要的东西,买了回来也是吃灰……
林羽凡 发布于 23 小时前(10月10日) 一个是不想折腾了,另一个有些觉得没必要的折腾或购买,想想就会算了。
李锋镝 发布于 23 小时前(10月10日) 少年老成吧大概是 :21:
Hary 发布于 23 小时前(10月10日) 我也是这样,感觉都不想折腾了,我还没到中年呢吧啊啊啊
标签聚合
日常 数据库 AI SpringBoot MySQL Claude SQL Theme ElasticSearch 架构 多线程 K8s Spring Redis JAVA 分布式 JVM AI编程 WordPress IDEA
友情链接
  • 搬砖日记
  • Honesty
  • 九仞之行
  • Serendipity
  • 韩情脉脉
  • 韩小韩博客
  • 林羽凡
  • 哥斯拉
  • 彬红茶日记
  • 懋和道人
  • 瓦匠个人小站
  • lijie blog
  • sssr7844的博客
  • 志文工作室
  • 老张博客
  • 皮皮社
  • 知向前端
  • Mr.Sun的博客

COPYRIGHT © 2016-2026 lifengdi.com. ALL RIGHTS RESERVED.

lifengdi.com 十年之约虫洞

Domain age badge for lifengdi.com

Theme Kratos-plus By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号