美团一面:OOM后,JVM一定会退出吗?为什么?

文章探讨了在Java中,当出现OOM(OutOfMemoryError)时,JVM并不一定会退出的情况。OOM是一个Error类型的异常,线程发生OOM时,会由JVM的默认异常处理器处理,但不会立即导致JVM退出,除非所有非守护线程因内存耗尽而结束。文中通过线程和线程池的实例解释了这一现象,并指出Linux的OOMkiller可能会在系统内存耗尽时强制结束JVM进程。此外,文章还提及这些知识在面试中的重要性。

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

说在前面

在40岁老架构师 尼恩的读者社区(50+)中,最近有小伙伴拿到了一线互联网企业如美团、拼多多、极兔、有赞、希音的面试资格,遇到一几个很重要的面试题:

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

生产环境,很多小伙伴,也遇到过OOM后,JVM会退出的问题。

这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典》V81版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲 》《尼恩Java面试宝典》 的PDF文件,请通过本公众号【技术自由圈】获取,暗号:领电子书

问题背景

问题是,咱们常常说:发生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相关面试题,是非常常见的面试题。

以上的5大方案,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。 offer, 也就来了。

学习过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

推荐阅读

顶奢好文:3W字,穿透Spring事务原理、源码,最少读10遍

腾讯太狠:40亿QQ号,给1G内存,怎么去重?

京东太狠:100W数据去重,用distinct还是group by,说说理由?

美团太狠:接口被恶刷10Wqps,怎么防?

以上尼恩 架构笔记、面试题 的PDF文件更新,▼请到下面【技术自由圈】公号取 ▼

<think>好的,我现在需要回答用户关于如何在JVM中查看和配置XX参数的问题。用户之前已经询问过JVM调优的参数,现在他们想具体了解如何查看和配置这些XX参数。首先,我得回忆一下JVM的相关知识,确保信息准确。 用户提到的XX参数通常是以-XX:开头,用于调整JVM的各种高级选项。这些参数可以分为布尔型和键值型两种。比如,-XX:+UseG1GC是布尔型,开启G1垃圾回收器;而-XX:MaxMetaspaceSize=256m则是键值型,设置元空间的最大大小。 接下来,用户想知道如何查看这些参数。我记得有几种方法。首先,使用java -XX:+PrintFlagsFinal命令可以显示所有可用的XX参数及其默认值。但输出内容很多,可能需要过滤,比如用grep或者findstr来查找特定参数。例如,在Linux下可以用java -XX:+PrintFlagsFinal | grep Metaspace来查找元空间相关的参数。此外,还有-XX:+PrintCommandLineFlags可以在启动时打印出用户显式设置的参数,这对于确认当前生效的配置很有用。 对于正在运行的JVM进程,可以使用jinfo工具来查看。比如jinfo -flags <pid>能显示所有参数,而jinfo -flag <参数名> <pid>可以查看具体参数的值。不过需要注意权限问题,可能需要sudo或者正确的用户权限。 在配置方面,用户需要知道如何将这些参数添加到启动命令中。通常是在启动Java应用时,在java命令后面加上这些参数,比如java -XX:+UseG1GC -Xmx2g -jar app.jar。同时,还要注意参数的类型,布尔型是+-号,键值型需要等号赋值。配置时机的话,像堆内存大小、垃圾回收器选择通常在应用启动前确定,而有些参数如Metaspace大小可能需要根据监控结果调整。 用户可能还会关心如何验证参数是否生效,这时候可以用前面提到的jinfo或者日志输出。例如,启用-XX:+PrintGCDetails后,GC日志会记录详细信息,确认参数起作用了。 还需要提醒用户注意参数的默认值和不同JVM版本的差异,比如G1在JDK9后的默认性。另外,生产环境配置前最好在测试环境验证,避免不稳定。如果用户遇到问题,可能需要检查参数拼写是否正确,或者是否适用于当前JVM版本。 最后,根据用户之前的引用,引用[1]提到了配置参数的场景和原则,引用[2]则展示了具体的参数配置示例,可以结合起来说明。例如,配置Metaspace大小和启用类加载日志的参数,这些都属于XX参数,用户可以参考这些例子来编写自己的启动命令。 总结来说,回答需要分步骤说明查看和配置的方法,提供具体命令示例,并引用相关资料支持,同时提醒注意事项,确保用户能够正确操作。</think>### 如何在JVM中查看和配置XX参数? #### 一、XX参数的基本概念 XX参数是JVM提供的高级配置选项,主要用于性能调优和功能控制。格式分为两种: 1. **布尔型参数**:`-XX:+<参数名>`(启用)或 `-XX:-<参数名>`(禁用) 示例:`-XX:+UseG1GC`(启用G1垃圾回收器) 2. **键值型参数**:`-XX:<参数名>=<值>` 示例:`-XX:MaxMetaspaceSize=256m`(设置元空间最大大小) --- #### 二、查看XX参数的方法 ##### 1. **查看所有默认参数** 使用命令 `java -XX:+PrintFlagsFinal`,会输出所有JVM参数及其默认值[^2]。 ```bash java -XX:+PrintFlagsFinal -version | grep Metaspace # Linux/Mac过滤示例 java -XX:+PrintFlagsFinal -version | findstr Metaspace # Windows过滤示例 ``` ##### 2. **查看已生效的XX参数** - **启动时打印显式配置的参数**: ```bash java -XX:+PrintCommandLineFlags -jar app.jar ``` - **查看运行中JVM的参数**: 使用 `jinfo` 工具(需安装JDK): ```bash jinfo -flags <pid> # 查看所有参数 jinfo -flag <参数名> <pid> # 查看具体参数值 ``` ##### 3. **通过日志输出特定参数** 部分参数可结合日志功能动态输出,例如: ```bash -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions ``` --- #### 三、配置XX参数的方法 ##### 1. **在启动命令中直接配置** ```bash java -XX:+UseG1GC -XX:MaxMetaspaceSize=256m -Xmx2g -jar app.jar ``` - **注意事项**: - `-Xmx`(堆最大值)和 `-Xms`(堆初始值)是标准参数,但属于广义的XX参数范畴[^1]。 - 布尔型参数需用 `+` 或 `-`,键值型参数用 `=` 赋值。 ##### 2. **通过环境变量配置** 在 `JAVA_OPTS` 中设置: ```bash export JAVA_OPTS="-XX:+UseG1GC -XX:MaxMetaspaceSize=256m" java $JAVA_OPTS -jar app.jar ``` ##### 3. **动态调整(部分参数支持)** 使用 `jinfo` 修改运行中JVM的某些参数(需参数支持热更新): ```bash jinfo -flag +HeapDumpOnOutOfMemoryError <pid> ``` --- #### 四、常用XX参数示例 | 参数类型 | 示例 | 用途说明 | |-------------------|----------------------------------------|------------------------------| | **垃圾回收** | `-XX:+UseG1GC` | 启用G1垃圾回收器 | | **内存管理** | `-XX:MaxMetaspaceSize=256m` | 限制元空间大小 | | **故障诊断** | `-XX:+HeapDumpOnOutOfMemoryError` | OOM时生成堆转储文件 | | **性能监控** | `-XX:+PrintGCDetails` | 输出GC详细日志 | --- #### 五、配置时机与原则[^1] 1. **配置时机**: - **启动前**:堆内存、垃圾回收器等核心参数。 - **运行中**:根据监控结果调整(如Metaspace扩容)。 2. **配置原则**: - **避免过度调优**:优先使用默认值,仅调整关键参数。 - **逐步验证**:每次只修改一个参数,通过日志和监控确认效果。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值