Java中内存问题之OOM详解

 更新时间:2023年08月22日 09:28:58   作者:ycfxhsw  
这篇文章主要介绍了Java中内存管理的OOM详解,OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error,需要的朋友可以参考下

一. StackOverflowError

1.1 bug

public class StackOverflowErrorDemo {
    public static void main(String[] args) {
        javaKeeper();
    }
    private static void javaKeeper() {
        javaKeeper();
    }
}

JVM 虚拟机栈是有深度的,在执行方法的时候会伴随着入栈和出栈,上边的方法可以看到,main 方法执行后不停的递归,迟早把栈撑爆了

Exception in thread "main" java.lang.StackOverflowError
	at oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)

1.2 原因分析

  • 无限递归循环调用(最常见原因),要时刻注意代码中是否有了循环调用方法而无法退出的情况
  • 执行了大量方法,导致线程栈空间耗尽
  • 方法内声明了海量的局部变量
  • native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 Linux)

1.3 解决方案

  • 修复引发无限递归调用的异常代码, 通过程序抛出的异常堆栈,找出不断重复的代码行,按图索骥,修复无限递归 Bug
  • 排查是否存在类之间的循环依赖(当两个对象相互引用,在调用toString方法时也会产生这个异常)
  • 通过 JVM 启动参数 -Xss 增加线程栈内存空间, 某些正常使用场景需要执行大量方法或包含大量局部变量,这时可以适当地提高线程栈空间限制

二. Java heap space

Java 堆用于存储对象实例,我们只要不断的创建对象,并且保证 GC Roots 到对象之间有可达路径来避免 GC 清除这些对象,那随着对象数量的增加,总容量触及堆的最大容量限制后就会产生内存溢出异常。

Java 堆内存的 OOM 异常是实际应用中最常见的内存溢出异常。

2.1 bug

/** * JVM参数&#xff1a;-Xmx12m */public class JavaHeapSpaceDemo {<!-- -->    static final int SIZE &#61; 2 * 1024 * 1024;    public static void main(String[] a) {<!-- -->        int[] i &#61; new int[SIZE];    }}

代码试图分配容量为 2M 的 int 数组,如果指定启动参数 -Xmx12m ,分配内存就不够用,就类似于将 XXXL 号的对象,往 S 号的 Java heap space 里面塞。

/**
 * JVM参数:-Xmx12m
 */
public class JavaHeapSpaceDemo {
    static final int SIZE = 2 * 1024 * 1024;
    public static void main(String[] a) {
        int[] i = new int[SIZE];
    }
}

2.2 原因分析

  • 请求创建一个超大对象,通常是一个大数组
  • 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值
  • 过度使用终结器(Finalizer),该对象没有立即被 GC
  • 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收

2.3 解决方案

针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:

  • 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制
  • 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
  • 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接

内存泄露和内存溢出

内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;

比如申请了一个 Integer,但给它存了 Long 才能存下的数,那就是内存溢出。

内存泄露( memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak 最终会导致 out of memory!

到此这篇关于Java中内存管理的OOM详解的文章就介绍到这了,更多相关Java的OOM内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解java线程的开始、暂停、继续

    详解java线程的开始、暂停、继续

    本文将介绍通过线程读取文件内容,并且可以控制线程的开始、暂停、继续,来控制读文件。具有一定的参考作用,下面跟着小编一起来看下吧
    2017-01-01
  • Kotlin委托机制使用方式和原理解析

    Kotlin委托机制使用方式和原理解析

    委托类(代理类)持有真实类的对象,然后委托类(代理类)调用真实类的同名方法,最终真正实现的是方法的是真实类,这其实就是代理模式,下面通过本文给大家介绍Kotlin委托机制使用方式和原理解析,感兴趣的朋友一起看看吧
    2025-06-06
  • 剑指Offer之Java算法习题精讲二叉树的构造和遍历

    剑指Offer之Java算法习题精讲二叉树的构造和遍历

    跟着思路走,之后从简单题入手,反复去看,做过之后可能会忘记,之后再做一次,记不住就反复做,反复寻求思路和规律,慢慢积累就会发现质的变化
    2022-03-03
  • JVM堆内存溢出后,其他线程是否可继续工作的问题解析

    JVM堆内存溢出后,其他线程是否可继续工作的问题解析

    这篇文章主要介绍了JVM 堆内存溢出后,其他线程是否可继续工作?,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • springboot 中 druid+jpa+MYSQL数据库配置过程

    springboot 中 druid+jpa+MYSQL数据库配置过程

    这篇文章主要介绍了springboot 中 druid+jpa+MYSQL数据库配置,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • Java中try、catch的使用方法

    Java中try、catch的使用方法

    这篇文章主要介绍了Java中try、catch的使用方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • 基于spring三方包类注入容器的四种方式小结

    基于spring三方包类注入容器的四种方式小结

    这篇文章主要介绍了基于spring三方包类注入容器的四种方式小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Springboot MongoDB实现自增序列的项目实践

    Springboot MongoDB实现自增序列的项目实践

    在某些特定的业务场景下,会需要使用自增的序列来维护数据,本文主要介绍了Springboot MongoDB实现自增序列的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 策略模式:告别if else

    策略模式:告别if else

    你是不是还在写着大量的if else语句,if else 不仅难以维护不易扩展,而且使代码臃肿不堪,想不想让你的业务代码更加的健壮,更易扩展,那你一定要学一学今天的主角策略模式
    2021-06-06
  • Java开发岗位面试被问到反射怎么办

    Java开发岗位面试被问到反射怎么办

    这篇文章主要介绍了java 面向对象面试集锦的相关资料,这里整理了面向对象的基础知识,帮助大家学习理解此部分的知识,需要的朋友可以参考下
    2021-07-07

最新评论