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

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
性能测试 PTS,5000VUM额度
应用实时监控服务-应用监控,每月50GB免费额度
简介: 之前通过三篇文章的分析,介绍了 直接内存、Metaspace和栈内存三块区域的内存溢出,同时给出了一些常见的引发内存溢出的场景以及对应解决方案,一般只要vm参数配置合理,代码上不出现大问题,一般不太容易引发对应的OOM

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

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

引发的起因

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

OOM-加载过程图解

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

OOM-加载过程图解 (1).png

老年代触发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-20210923232157906

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

小结:

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

目录
相关文章
|
3月前
|
监控 Java
压力测试Jmeter的简单使用,性能监控-堆内存与垃圾回收 -jvisualvm的使用
这篇文章介绍了如何使用JMeter进行压力测试,包括测试前的配置、测试执行和结果查看。同时,还探讨了性能监控工具jconsole和jvisualvm的使用,特别是jvisualvm,它可以监控内存泄露、跟踪垃圾回收、执行时内存和CPU分析以及线程分析等,文章还提供了使用这些工具的详细步骤和说明。
压力测试Jmeter的简单使用,性能监控-堆内存与垃圾回收 -jvisualvm的使用
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
30 4
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
62 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
32 2
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
2月前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
1月前
|
C++
析构造函数就是为了释放内存,就是在局部指针消失前释放内存,拷贝构造函数就是以构造函数为模块,在堆里面新开一块,同一个变量在堆里面的地址
本文讨论了C++中构造函数和析构函数的作用,特别是它们在管理动态内存分配和释放中的重要性,以及如何正确地实现拷贝构造函数以避免内存泄漏。
37 2
|
1月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
28 0
深入理解java对象的内存布局
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
1月前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。