JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。

最近读书心得:在建立养成终身成长型思维过程,要重视熵减生活、工作。眼花缭乱的纷杂社会,很容易让我们进入盲目焦虑的状态。坚壁清野,唯清唯静。

     JVM偏重实战经验的面试,面试官开局都喜欢问这个题目,这个题可以直接考察JVM内存模型基础以及候选人的实战经验,可谓一举两得。候选人也许内心万马奔腾,但是这个确实很考验基础,属于半开放的万能考题。根据候选人的履历,可考察很深入、也可以考察比较浅。能问出这样问题的面试官、以及答好这样的问题的候选人,都有一个共性,就是及其重视基础,韧性十足的扫地憎。能耐心答完这个问题,距离满意的offer将大幅接近。

      我们本系列JVM调优目标,除了达成让系统服务运行更流畅、延时更低,也要达到避免OOM的目的。今天重点分析OOM的种类,并结合示例Demo分析总结OOM原因,帮助有缘刷到的同学巩固掌握OOM这块领域。「拉丁解牛说技术,实用至上,坚持用最简洁直白的文字+最少的代码示例分享干货。」

一、内存泄漏与内存溢出的区别

     内存泄漏Memory Leak,通俗的讲,就是有一部分内存空间被无效的持续占用,导致这部分内存无法回收重复利用。比如在《JAVA并发编程系列(12)ThreadLocal就是这么简单》我们说过,ThreadLocal就可能会发生内存泄漏问题。比如内存只有10Mb,但是程序申请分配了4Mb内存空间,程序运行自始至终从没应用到这4Mb无效内存,GC永远回收不到它。导致内存泄漏了4Mb,内存最终可用空间只有6Mb。

     内存溢出Out of Memory(简称OOM),是指超过可用内存大小=内存应用超标爆表。比方说,内存只有10Mb,如果要放11Mb数据,就超出了内存可用大小,发生OOM。

     而今天的主角内存溢出,按之前《JVM进阶调优系列(2)JVM内存区域怎么划分,分别有什么用》说的那样,内存溢出会发生在heap堆内存、Metaspace元数据区、stack栈内存溢出、直接内存DirectMemory溢出四大种类。

二、heap堆内存溢出

      按之前说过堆内存分年轻代+老年代,发生Out Of Memory Error异常,真实场景实质就是GC之后,仍然无法腾出足够可用内存空间分配给新对象。JVM被迫终止运行退出。

2.1 示例代码Demo

      我们模拟内存溢出,通过设置10Mb堆内存,尝试创建6个2Mb的对象(实际5个都分配不了,就OOM)。

* JVM参数:-Xms10m -Xmx10m -Xmn5m -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

* 参数说明:整个堆内存10Mb,其中老年代5Mb,年轻代5Mb,此外:

* -XX:+HeapDumpOnOutOfMemoryError ,内存溢出时导出整个堆信息,让JVM遇到OOM异常时能输出堆内信息;

* -XX:HeapDumpPath=./,内存异常堆数据导出到当前目录。

package lading.java.jvm;
public class Demo005OOM {
    public static void main(String[] args) {
        byte[] obj_2Mb_1 = new byte[2 * 1024 * 1024];
        byte[] obj_2Mb_2 = new byte[2 * 1024 * 1024];
        byte[] obj_2Mb_3 = new byte[2 * 1024 * 1024];
        byte[] obj_2Mb_4 = new byte[2 * 1024 * 1024];
        byte[] obj_2Mb_5 = new byte[2 * 1024 * 1024];
        byte[] obj_2Mb_6 = new byte[2 * 1024 * 1024];
    }
}

2.2 堆内存溢出日志

     运行后,出现了Exception in thread "main" java.lang.OutOfMemoryError: Java heap space异常。

2.3 堆内存溢出原因分析

     OOM的原因只有2个,一个是我们设置的JVM内存太小,不能满足业务系统合理要求。另一个是代码写的有问题,导致内存申请急速膨胀。

2.3.1 JVM内存分配不合理的几种表现

1、整个-Xms分配太小,系统正常运行没多久就OOM。

2、年轻代和老年代分配不合理,比如年轻代和老年代的比例是9:1,YGC后存活对象很多,老年代放不下,发生了OOM。

3、年轻代的S区过小,每次YGC后存活对象都进入老年代,而老年代的对象又无法回收,导致OOM。

2.3.2 代码问题导致OOM的几种表现

1、一次加载过多无价值的数据,导致OOM。比如有个超大表,本次查询只需要其中2列,但是使用了select * from ,加载了很多没必要的数据导致堆内存急速膨胀。

2、代码bug,有无限循环递归。就出现无限在创建新对象导致OOM。

3、内存泄漏导致内存溢出。比如无效的对象,长期无法被回收,而且还在不断新增,日积月累就发生OOM。

     此外,业务量暴增、或者发生雪崩,导致某个服务实例请求大幅上升,让之前评估合理的JVM配置无法适配当前高并发访问导致OOM。

三、stack栈内存溢出

    在内存区域划分专栏里说过,每个线程都有自己的虚拟机栈,当线程执行一个方法时,会为该方法创建栈帧,用来存放方法里的局部变量引用、方法的出口、动态链接等信息。-Xss一般就是512k,或者1Mb。看系统并发能力以及内存情况来设置。方法执行存在递归死循环,或者方法里面有非常多的代码,比如几万行(这种不太可能),线程执行这种方法就一定会出现java.lang.StackOverflowError异常。

3.1 示例代码Demo

    我们通过设置-Xss256k,让某方法递归调用自己,模拟栈内存溢出异常。

完整JVM参数:-Xms10m -Xmx10m -Xmn5m -Xss256k。

package lading.java.jvm;
public class Demo006StackOverError {
    public static int count = 1;
    /**
     * 该方法将无限递归调用自己,类似while(true)
     */
    public static void stackOverDemo() {
        count++;
        stackOverDemo();
    }
    public static void main(String[] args) {
        stackOverDemo();
    }
}

3.2 栈内存溢出日志

     程序运行异常退出,发生了java.lang.StackOverflowError异常。

3.3 栈内存溢出原因分析

     虚拟机栈出现异常,基本就是代码编写不当,导致方法执行被递归深度过大,甚至无限递归。所以代码的重试机制、while(条件)语句、for(条件)、还有方法里的递归,这些地方要多注意是否会出现Stack over flow error问题。

     虚拟机栈的内存大小-Xss其实一般配置1M足够了。如果JVM内存不大和并发不高,设置256k,或者512k也足够用。特殊的数据计算或者涉及要递归,如果合理,可以提高到2Mb大小。

四、Metaspace元数据区溢出

      元数据区,也是多线程共享区域,但是和我们常说的堆内存不是同一个区域。元数据区主要存放的是,.class字节码、运行时常量池、JIT即时编译后的机器码。控制元数据区大小,主要有2个参数,第一个是 MetaspaceSize,可以设置元空间的初始大小。在JVM启动时,就从系统内存申请分配该大小的内存给元数据区。另一个MaxMetaspaceSize,可以设置元空间的最大大小,这个很有必要,因为默认情况下,元空间的大小可以无限申请达到最大物理内存,通过设置该参数来设置元数据空间上限,避免内存泄漏。

4.1 示例代码Demo

     我们通过cglib Enhancer 多次动态加载LadingShare.class,并对它的startWork方法进行增强。模拟元数据区内存溢出场景。元数据可用空间限制仅提供10Mb,JVM参数:-Xms10m -Xmx10m -Xmn5m -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M。

package lading.java.jvm;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Demo007MetaspaceOOM {
    public static class LadingShare {
        public LadingShare() {
        }
        /**
         * 开始创作
         */
        public void startWork() {
            System.out.println("拉丁解牛说技术,技术分享开始创作ing");
        }
    }
    static class MethodPlus implements MethodInterceptor {
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("LadingShare.startWork()创作前,先构思思路。");
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("LadingShare.startWork()创作后,到技术平台发布分享。");
            return result;
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            System.out.println("当前加载" + ++i + "个LadingShare.class");
            //创建Enhancer对象,动态代理LadingShare.class,并对它的startWork()方法新增而外逻辑
            Enhancer enhancer = new Enhancer();
            LadingShare share = new LadingShare();
            enhancer.setSuperclass(share.getClass());
            enhancer.setUseCache(false);//这里很关键,不能用cache,否则不会OutOfMemoryError-->Metaspace
            enhancer.setCallback(new MethodPlus());
            // 创建代理对象
            LadingShare proxy = (LadingShare) enhancer.create();
            // 调用代理对象的方法
            proxy.startWork();
        }
    }
}

4.2 栈内存溢出日志

      抛出了org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace

以及Caused by: java.lang.OutOfMemoryError: Metaspace异常。

4.3 栈内存溢出原因分析

      当我们设置的metaspace空间太小,而项目代码特别多,项目启动的时候就会报元数据内存溢出。另外一种情况就是demo我们示范的,项目运行过程,存在cglib、ByteBuddy等字节码增强或动态代理技术应用,会动态的生成加载类,需要注意是否存在元数据区OOM问题。

五、DirectMemory直接内存溢出

      在JVM堆内存之外,除了有虚拟机栈内存、元数据区,还有一个DirectMemory直接内存区。这个区域的作用是什么呢?「拉丁解牛说技术,实用至上,坚持用最简洁直白的文字+最少的代码示例分享干货。」

      直接内存的设置,主要是java NIO库的需要。NIO对数据读写要求高,且内存需求大,如果直接使用堆内存进行NIO频繁操作,JVM的堆内存很快就被打满,随后发生频繁的GC。而在堆内存之外的直接内存,有读写效率高、内存空间相对独立且容量够大的特点,非常适合NIO库的应用场景。

      如果我们要用到直接内存,可以通过java.nio.ByteBuffer.allocateDirect()进行申请,也可以通过java.nio.DirectByteBuffer操作直接内存。直接内存我们是无法通过jmap查看,只能通过类似top 命令来看它的内存使用情况。

      直接内存的最大可用空间大小,可以通过-XX:MaxDirectMemorySize来限制。当发生FGC后,这部分内存也会被GC回收。如果GC后,新申请的直接内存,大于直接内存可用空间,就会报直接内存OOM。

5.1 示例代码Demo

       这里我们模拟,设置10Mb的直接内存空间,通过nio..ByteBuffer.allocateDirect()不断申请直接内存,最后导致OOM的简单案例。

JVM参数:主要设置10Mb的直接内存。

 -Xms10m -Xmx10m -Xmn5m -XX:MaxDirectMemorySize=10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

package lading.java.jvm;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
/**
 * 模拟直接内存溢出场景
 */
public class Demo008DirectMemoryOOM {
    public static void main(String[] args) {
        List<ByteBuffer> directMemList = new LinkedList<>();
        for (int i = 0; i < 10; i++) {
            //每次申请2Mb 直接内存
            System.out.println("尝试申请第:" + i + "个2Mb的直接内存");
            ByteBuffer directMem2Mb = ByteBuffer.allocateDirect(2 * 1024 * 1024);
            directMemList.add(directMem2Mb);
        }
    }
}

5.2 DirectMemory直接内存溢出日志

     程序发生了java.lang.OutOfMemoryError: Direct buffer memory异常。

5.3 DirectMemory直接内存溢出原因分析

      直接内存的应用,常见的就是NIO,比如Netty框架。出现了DM OOM,一个可能是研发预估不足,没有设置直接内存大小,或者分配的大小不合理。系统上线前,可以通过压测来合理设置-XX:MaxDirectMemorySize参数。

      另一个是没有主动做好回收。研发人员需要注意手工回收这部分内存,比如可以通过DirectBuffer.cleaner().clean();进行回收。像Netty这种框架,他们会对直接内存进行主动充分管理。

六、内存溢出如何分析定位问题

      之前《系列(7)JVM调优监控必备命令、工具集合》有详细分享通过jmap、jhat、GCeasy、Arthas等命令工具进行分析堆内存、GC情况。这个足以对堆内存溢出、虚拟机栈内存溢出问题、以及元数据区的溢出进行全面分析排查。

      唯独直接内存溢出,如何排查分析呢?首先我们一定要设置 -XX:MaxDirectMemorySize参数,否则当程序申请过大直接内存后,会被Docker、系统悄无声息的干掉,将不会留下没有任何痕迹,就很难排查。

     这里devops建设完善的公司,会对系统进行全面的监控,当超过阈值收到告警后,我们可以及时跟进分析服务状态。

     另外,可以通过设置JVM参数-XX:NativeMemoryTracking=summary | detail参数来分析追踪JVM内存情况,这个参数由于导致5%-10%的额外性能开销,一般不启用。设置该参数,启动系统服务后,可以通过命令

   jcmd 【jvm进程id】 VM.native_memory 查看实时内存分配情况。

此外,也可以再增加-XX:+PrintNMTStatistics、-XX:+UnlockDiagnosticVMOptions参数,当启用 NativeMemoryTracking 时,让JVM退出时打印内存使用情况。这样可以帮助排查发生直接内存溢出时的系统情况。

推荐阅读:

1、JVM进阶调优系列(3)堆内存的对象什么时候被回收?

2、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?

3、JVM进阶调优系列(1)类加载器原理一文讲透

4、JAVA并发编程系列(13)Future、FutureTask异步小王子

相关文章
|
4天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
8天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
4天前
|
人工智能 运维 双11
2024阿里云双十一云资源购买指南(纯客观,无广)
2024年双十一,阿里云推出多项重磅优惠,特别针对新迁入云的企业和初创公司提供丰厚补贴。其中,36元一年的轻量应用服务器、1.95元/小时的16核60GB A10卡以及1元购域名等产品尤为值得关注。这些产品不仅价格亲民,还提供了丰富的功能和服务,非常适合个人开发者、学生及中小企业快速上手和部署应用。
|
13天前
|
人工智能 弹性计算 文字识别
基于阿里云文档智能和RAG快速构建企业"第二大脑"
在数字化转型的背景下,企业面临海量文档管理的挑战。传统的文档管理方式效率低下,难以满足业务需求。阿里云推出的文档智能(Document Mind)与检索增强生成(RAG)技术,通过自动化解析和智能检索,极大地提升了文档管理的效率和信息利用的价值。本文介绍了如何利用阿里云的解决方案,快速构建企业专属的“第二大脑”,助力企业在竞争中占据优势。
|
15天前
|
自然语言处理 数据可视化 前端开发
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
合合信息的智能文档处理“百宝箱”涵盖文档解析、向量化模型、测评工具等,解决了复杂文档解析、大模型问答幻觉、文档解析效果评估、知识库搭建、多语言文档翻译等问题。通过可视化解析工具 TextIn ParseX、向量化模型 acge-embedding 和文档解析测评工具 markdown_tester,百宝箱提升了文档处理的效率和精确度,适用于多种文档格式和语言环境,助力企业实现高效的信息管理和业务支持。
3936 2
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
|
4天前
|
算法 安全 网络安全
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
2024阿里云11.11金秋云创季活动火热进行中,活动月期间(2024年11月01日至11月30日)通过折扣、叠加优惠券等多种方式,阿里云WoSign SSL证书实现优惠价格新低,DV SSL证书220元/年起,助力中小企业轻松实现HTTPS加密,保障数据传输安全。
502 3
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
|
11天前
|
安全 数据建模 网络安全
2024阿里云双11,WoSign SSL证书优惠券使用攻略
2024阿里云“11.11金秋云创季”活动主会场,阿里云用户通过完成个人或企业实名认证,可以领取不同额度的满减优惠券,叠加折扣优惠。用户购买WoSign SSL证书,如何叠加才能更加优惠呢?
985 3
|
8天前
|
机器学习/深度学习 存储 人工智能
白话文讲解大模型| Attention is all you need
本文档旨在详细阐述当前主流的大模型技术架构如Transformer架构。我们将从技术概述、架构介绍到具体模型实现等多个角度进行讲解。通过本文档,我们期望为读者提供一个全面的理解,帮助大家掌握大模型的工作原理,增强与客户沟通的技术基础。本文档适合对大模型感兴趣的人员阅读。
412 17
白话文讲解大模型| Attention is all you need
|
8天前
|
算法 数据建模 网络安全
阿里云SSL证书2024双11优惠,WoSign DV证书220元/年起
2024阿里云11.11金秋云创季火热进行中,活动月期间(2024年11月01日至11月30日),阿里云SSL证书限时优惠,部分证书产品新老同享75折起;通过优惠折扣、叠加满减优惠券等多种方式,阿里云WoSign SSL证书将实现优惠价格新低,DV SSL证书220元/年起。
560 5
|
4天前
|
安全 网络安全
您有一份网络安全攻略待领取!!!
深入了解如何保护自己的云上资产,领取超酷的安全海报和定制鼠标垫,随时随地提醒你保持警惕!
697 1
您有一份网络安全攻略待领取!!!