40-对象太多了!堆内存实在是放不下,只能内存溢出!

·  阅读 119
40-对象太多了!堆内存实在是放不下,只能内存溢出!

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

欢迎关注公众号OpenCoder,来和我做朋友吧~❤😘😁🐱‍🐉👀

之前通过三篇文章的分析,介绍了 直接内存、Metaspace和栈内存三块区域的内存溢出,同时给出了一些常见的引发内存溢出的场景以及对应解决方案,一般只要vm参数配置合理,代码上不出现大问题,一般不太容易引发对应的OOM。再次通过下图进行回顾:

图片

而本篇文章介绍的堆内存OOM,就是我们的重点了,这块区域才是真正最容易引发内存溢出的。

引发的起因

我们在上图中其实可以发现,每次大量调用方法的时候,方法中的代码都会有创建对象的操作,那么会导致大量的对象进入到新生代,如果新生代放不下,触发Minor GC,则会转移存活对象进入 Survior区域,如果遇到高并发场景下,导致Minor GC过后依然有很多请求未处理完毕,存活对象太多,导致Survior区域放不下,直接进入老年代,如下图所示:

图片

一旦当老年代也满了或达到阈值,就会触发Full GC,如果此时老年代GC后发现依然剩下很多存活对象,而新生代GC后需要转移的对象又很多,想放入老年代存放,发现老年代也放不下了,那么此时就会导致OOM,如下图所示

图片

老年代触发GC后依然无法存放新生代转移过来的对象,没有足够的空间还要继续转移,那么就导致OOM。这就是一种典型的堆内存放不下而导致的内存溢出的一个案例。

堆内存溢出场景总结

一般发生堆内存溢出的场景主要有两种:

  1. 高并发场景,由于请求量非常大,导致大量对象都是存活状态,而大量存活对象放入有限的空间放不下,从而引发OOM,系统崩溃。
  2. 内存泄露场景,系统中存在内存泄露的问题,莫名其妙的产生了大量的对象,并且是存活状态,没有及时取消对象的引用,导致触发GC后还是无法回收,从而引发内存溢出。

因此,总结下就是导致内存泄露的原因:要不就是系统负载过高,要不就是内存泄露问题。

代码模拟堆内存OOM场景

我们通过以下代码来进行模拟:

/**
* vm参数:-Xms 10m -Xmx 10m
*/
public class OOMTest1 {
   public static void main(String[] args) {
       int count = 0;
       List<Object> list = new ArrayList<>();
       while(true){
           list.add(new Object());
           System.out.println("当前创建了第"+(++count)+"个对象");
      }
  }
}
复制代码

代码很简单就是通过无限循环,往一个集合里添加对象,而集合是个强引用对象不会被回收,因此当Eden区存满后 ,存活对象均会进入到老年代,直到老年代也装不下后,触发OOM。

打印结果:

image.png

在10M的堆内存中,通过最简单的Object对象想要将内存撑满,也需要大概36万个对象。并且控制台里也有明确的提示:OutOfMemoryError : Java heap space 指向堆内存区域。

小结:

ok,通过以上的讲解,我们对堆内存发生OOM的根本原因有了一个理解,以及两种触发堆内存OOM的场景总结,以及通过简单的代码快速模拟了堆内存的OOM溢出。

欢迎关注公众号OpenCoder,来和我做朋友吧~❤😘😁🐱‍🐉👀

分类:
后端
标签:
分类:
后端
标签: