美团面试:说说OOM三大场景和解决方案? (绝对史上最全)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 小伙伴们,有没有遇到过程序突然崩溃,然后抛出一个OutOfMemoryError的异常?这就是我们俗称的OOM,也就是内存溢出本文来带大家学习Java OOM的三大经典场景以及解决方案,保证让你有所收获!

首先,咱们先聊聊,什么是OOM?

小伙伴们,有没有遇到过程序突然崩溃,然后抛出一个OutOfMemoryError的异常?这就是我们俗称的OOM,也就是内存溢出。简单来说,就是你的Java应用想要的内存超过了JVM愿意给的极限,就会抛出这个错误。
那么为什么会出现OOM呢?一般都是由这些问题引起:

  1. 分配过少:JVM 初始化内存小,业务使用了大量内存;或者不同 JVM 区域分配内存不合理

  2. 内存泄漏:某一个对象被频繁申请,不用了之后却没有被释放,发生内存泄漏,导致内存耗尽(比如ThreadLocal泄露)

接下来,我们来聊聊Java OOM的三大经典场景以及解决方案,保证让你有所收获!👍

Java OOM的三大核心场景

场景一:堆内存OOM(也叫堆内存溢出)

这是最常见的OOM场景了,发生在JVM试图分配对象空间时,却发现剩余的堆内存不足以存储新对象。

例如我们执行下面的代码,就可以模拟出堆内存OOM的场景:

// 创建大量对象导致堆内存溢出
public class HeapOOM {
   
    static class OOMObject {
   
        // 假设这里有一些属性
    }

    public static void main(String[] args) {
   
        List<OOMObject> list = new ArrayList<>();

        while (true) {
   
            list.add(new OOMObject()); // 不断创建对象并添加到list中
        }
    }
}
AI 代码解读

那么当出现线上应用OOM场景时,该如何解决呢?

分析方法通常有两种:

  • 类型一:在线分析,属于轻量级的分析:

  • 类型二:离线分析,属于重量级的分析:

类型一:在线OOM分析:

在线分析Java OOM(内存溢出)问题,通常涉及到监控运行中的Java应用,捕获内存溢出时的信息,分析堆转储(Heap Dump)文件,以及利用一些工具和命令来辅助定位问题。下面是一套详细的分析流程和命令,帮助你在线分析和解决Java OOM问题:

1、启用JVM参数以捕获Heap Dump

在Java应用启动命令中加入以下JVM参数,以确保在发生OOM时能自动生成堆转储文件:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof
AI 代码解读

这些参数的作用是:

  • -XX:+HeapDumpOnOutOfMemoryError:指示JVM在遇到OOM错误时生成堆转储文件。
  • -XX:HeapDumpPath:指定堆转储文件的存储路径,可以自定义路径和文件名。

2、实时监控内存使用情况

使用jvisualvmjconsole等工具可以实时监控Java应用的内存使用情况。这些工具可以帮助你了解内存消耗的趋势,从而预测和避免OOM的发生。

  • JVisualVM:集成了多个JDK命令行工具,提供了可视化界面,可以监控内存使用、查看线程、分析堆等。
  • JConsole:Java监控和管理控制台,用于对JVM中的内存、线程和类等进行监控。

3、分析Heap Dump文件

当应用抛出OOM并且根据上述设置生成了堆转储文件后,使用Heap Dump分析工具来分析这个文件。常用的工具有:

  • Eclipse Memory Analyzer (MAT):一个强大的Java堆分析工具,可以帮助识别内存泄露和查看内存消耗情况。
  • VisualVM:除了监控功能外,也支持加载和分析Heap Dump文件。

在MAT中打开Heap Dump文件,主要关注以下几点:

  • 查找内存中对象的分布,特别是占用内存最多的对象。
  • 分析这些对象的引用链,确定是哪部分代码引起的内存泄漏或过度消耗。
  • 检查ClassLoader,以确认是否有过多的类被加载导致的元空间(Metaspace)OOM。

4、使用命令行工具

JDK提供了一些命令行工具,如jmap,可以用来生成Heap Dump文件:

jmap -dump:live,format=b,file=heapdump.hprof <pid>
AI 代码解读

其中<pid>是Java进程的ID。-dump:live选项表示只转储活动对象,可以减小Heap Dump文件的大小。

5、分析日志和异常信息

最后,不要忽视应用的日志和抛出的异常信息。OOM之前的日志可能会提供一些导致内存溢出的操作或业务逻辑的线索。

类型二:离线OOM分析,这个属于重量级分析

离线分析Java OOM(OutOfMemoryError)通常是在问题发生后,通过分析JVM生成的堆转储(Heap Dump)文件来进行。这个过程涉及到获取堆转储文件、使用分析工具进行深入分析和解读分析结果

1、获取Heap Dump文件

首先,确保你已经有了一个Heap Dump文件。这个文件可能是在JVM遇到OOM时自动生成的(如果启用了-XX:+HeapDumpOnOutOfMemoryError JVM参数),或者你可以在应用运行期间手动生成:

  • 使用jmap命令生成Heap Dump文件:

    jmap -dump:live,format=b,file=/path/to/heapdump.hprof <pid>
    
    AI 代码解读

    其中<pid>是Java进程的ID,/path/to/heapdump.hprof是你希望保存Heap Dump文件的位置。

2、使用Heap Dump分析工具

有了Heap Dump文件后,你需要使用专门的工具来进行分析。以下是一些常用的分析工具:

  • Eclipse Memory Analyzer (MAT):非常强大的内存分析工具,能帮助识别内存泄漏和查看内存消耗情况。
  • VisualVM:提供了一个可视化界面,可以用来分析Heap Dump文件。
  • JVisualVM:随JDK一起提供的工具,也支持加载Heap Dump文件进行分析。

3、分析Heap Dump文件

使用MAT(Eclipse Memory Analyzer)作为示例,分析流程如下:

  1. 打开Heap Dump文件:启动MAT并打开Heap Dump文件(.hprof)。

  2. 运行Leak Suspects Report:MAT可以自动生成一个内存泄漏报告(Leak Suspects Report),这个报告会指出可能的内存泄漏路径。

  3. 分析Dominators Tree:这个视图显示了占用最多内存的对象及其引用。通过它,你可以找到最大的内存消耗者。

  4. 查看Histogram:对象Histogram列出了所有对象的实例数和总大小,帮助你识别哪种类型的对象占用了最多的内存。

  5. 检查GC Roots:为了确定对象为什么没有被垃圾回收,可以查看对象到GC Roots的引用链。

  6. 分析引用链:通过分析对象的引用链,你可以确定是什么持有了这些对象的引用,导致它们无法被回收。

下面给大家提供一份Java应用上线前参考的的JVM配置(内存8G),以后系统上线前可以先配置下JVM,不要啥都不配置就上线了

-Xms6g -Xmx6g (按不同容器,4G及以下建议为50%,6G以上,建议设置为70%)
-Xmn2g    (以8G内存,年轻代可以设置为2G)
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-Xss256k
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:AutoBoxCacheMax=20000
-XX:+HeapDumpOnOutOfMemoryError (当JVM发生OOM时,自动生成DUMP文件)
-XX:HeapDumpPath=/usr/local/logs/gc/
-XX:ErrorFile=/usr/local/logs/gc/hs_err_%p.log (当JVM发生崩溃时,自动生成错误日志)
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/usr/local/heap-dump/
AI 代码解读

场景二:元空间(MetaSpace)OOM

什么是元空间?

Java元空间(Metaspace)是Java虚拟机(JVM)中用于存放类的元数据的区域,从Java 8开始引入,替代了之前的永久代(PermGen)

图中红色箭头所指就是元空间

元空间是方法区在HotSpot JVM 中的实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。

元空间(Metaspace) 垃圾回收,会对僵死的类及类加载器的垃圾回收会进行回收,元空间(Metaspace) 垃圾回收的时机是,在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。

元空间OOM的现象

JVM 在启动后或者某个时间点开始,MetaSpace 的已使用大小在持续增长,同时每次 GC 也无法释放,调大 MetaSpace 空间也无法彻底解决

元空间OOM的核心原因:生成了大量动态类

比如:

  1. 使用大量动态生成类的框架(如某些ORM框架、动态代理技术、热部署工具等)

  2. 程序代码中大量使用反射,反射在大量使用时,因为使用缓存的原因,会导致ClassLoader和它引用的Class等对象不能被回收

例如下面的生成大量动态代理类的代码示例,则会导致元空间的OOM

// 使用CGLIB动态生成大量类导致元空间溢出
public class MetaspaceOOM {
   
    public static void main(String[] args) {
   
        while (true) {
   
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
   
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
   
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create(); // 动态生成类并加载
        }
    }

    static class OOMObject {
   
        // 这里可以是一些业务方法
    }
}
AI 代码解读

元空间(Metaspace) OOM 解决办法:

  1. 减少程序中反射的大量使用

  2. 做好熔断限流措施,对应用做好过载保护,比如阿里的sentinel限流熔断中间件

场景三:堆外内存OOM

Java对外内存(Direct Memory)OOM指的是Java直接使用的非堆内存(off-heap memory)耗尽导致的OutOfMemoryError。这部分内存主要用于Java NIO库,允许Java程序以更接近操作系统的方式管理内存,常用于高性能缓存、大型数据处理等场景

例如下面的代码,如何堆外内存太小,就会导致堆外内存的OOM:

// 分配大量直接内存导致OOM
import java.nio.ByteBuffer;

public class DirectMemoryOOM {
   
    private static final int ONE_MB = 1024 * 1024;

    public static void main(String[] args) {
   
        int count = 1;

        try {
   
            while (true) {
   
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(ONE_MB);
                count++;
            }
        } catch (Exception e) {
   
            System.out.println("Exception: instance created " + count);
            throw e;
        }
    }
}
AI 代码解读

堆外内存的原因

  • 分配过量的直接内存:程序中大量使用DirectByteBuffer等直接内存分配方式,而没有相应的释放机制,导致内存迅速耗尽,常见于NIO、Netty等相关组件。
  • 内存泄露:如果分配的直接内存没有被及时释放(例如,ByteBuffer未被回收),就可能发生内存泄露。
  • JVM对外内存限制设置不当:通过-XX:MaxDirectMemorySize参数控制对外内存大小,如果设置过小,可能无法满足应用需求。

堆外内存OOM的解决方案

  • 合理设置对外内存大小:根据应用的实际需求调整-XX:MaxDirectMemorySize参数,给予足够的直接内存空间。
  • 优化内存使用:减少不必要的直接内存分配,重用DirectByteBuffer等资源。
  • 内存泄露排查:使用工具(如VisualVM、JProfiler等)定位和解决内存泄露问题。
  • 代码优化:确保使用完直接内存后显式调用sun.misc.Cleaner.clean()或通过其他机制释放内存。

最后说一句(求关注,求赞,别白嫖我)

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
21
分享
相关文章
美团面试:MySQL为什么 不用 Docker部署?
45岁老架构师尼恩在读者交流群中分享了关于“MySQL为什么不推荐使用Docker部署”的深入分析。通过系统化的梳理,尼恩帮助读者理解为何大型MySQL数据库通常不使用Docker部署,主要涉及性能、管理复杂度和稳定性等方面的考量。文章详细解释了有状态容器的特点、Docker的资源隔离问题以及磁盘IO性能损耗,并提供了小型MySQL使用Docker的最佳实践。此外,尼恩还介绍了Share Nothing架构的优势及其应用场景,强调了配置管理和数据持久化的挑战。最后,尼恩建议读者参考《尼恩Java面试宝典PDF》以提升技术能力,更好地应对面试中的难题。
美团面试:手机扫描PC二维码登录,底层原理和完整流程是什么?
45岁老架构师尼恩详细梳理了手机扫码登录的完整流程,帮助大家在面试中脱颖而出。该过程分为三个阶段:待扫描阶段、已扫描待确认阶段和已确认阶段。更多技术圣经系列PDF及详细内容,请关注【技术自由圈】获取。
每日大厂面试题大汇总 —— 今日的是“美团-后端开发-一面”
文章汇总了美团后端开发一面的面试题目,内容涉及哈希表、HashMap、二叉树遍历、数据库索引、死锁、事务隔离级别、Java对象相等性、多态、线程池拒绝策略、CAS、设计模式、Spring事务传播机制及RPC序列化工具等。
110 0
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
45岁资深架构师尼恩在其读者群中分享了关于如何提升RocketMQ顺序消费性能的高并发面试题解析。面对10W QPS的高并发场景,尼恩详细讲解了RocketMQ的调优策略,包括专用方案如增加ConsumeQueue数量、优化Topic设计等,以及通用方案如硬件配置(CPU、内存、磁盘、网络)、操作系统调优、Broker配置调整、客户端配置优化、JVM调优和监控与日志分析等方面。通过系统化的梳理,帮助读者在面试中充分展示技术实力,获得面试官的认可。相关真题及答案将收录于《尼恩Java面试宝典PDF》V175版本中,助力求职者提高架构、设计和开发水平。
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
【Vue面试题十四】、说说你对vue的mixin的理解,有什么应用场景?
这篇文章详细介绍了Vue中`mixin`的概念、应用场景和源码分析,解释了`mixin`如何用于代码复用、功能模块化,并通过实际代码示例展示了在Vue组件中局部混入和全局混入的使用方式。
【Vue面试题十四】、说说你对vue的mixin的理解,有什么应用场景?
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
数据库乐观锁是必知必会的技术栈,也是大厂面试高频,十分重要,本文解析数据库乐观锁。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
美团面试:String 为什么 不可变 ?(90%答错了,尼恩来一个绝世答案)
45岁老架构师尼恩分享Java面试心得,涵盖String不可变性、字符串常量池、面试技巧等内容。尼恩强调,掌握深层技术原理,如String不可变性的真正原因,可在面试中脱颖而出,赢得高薪Offer。此外,尼恩还提供了大量技术资源和面试指导,帮助求职者提升技术水平,顺利通过大厂面试。
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?

热门文章

最新文章