JVM实战(30)——模拟堆内存溢出

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

本章,我们将通过示例代码演示Java堆内存区域是如何发生内存溢出的,并根据内存快照进行分析。

我们回顾下堆内存溢出的一个场景:系统负载很高,不停的在Eden区创建新对象,直到触发Young GC,但是由于并发太高,Young GC发现Eden区存活对象非常多,Survivor无法容纳,只能把大批存活对象转移到老年代。经过几次这种Young GC之后,老年代也满了,于是触发Full GC,但是Full GC之后老年代里还是塞满了对象,导致Young GC过后的存活对象无处可安放,最终引发堆内存溢出。

二、示例程序

2.1 程序源码

复制代码
    package com.mx.jvm;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo3 {
        public static void main(String[] args) {
            long counter = 0L;
            List<Object> list = new ArrayList<Object>();
    
            while (true) {
                list.add(new Object());
                System.out.println("当前创建了第" + ++counter + "个对象");
            }
        }

上述代码很简单,就是不停的创建对象,由于对象由一个while循环外部的List引用着,且main()方法是无限执行的,所以这些创建的对象始终不会被回收掉。这样最终Eden区、老年代的空间都会被占满。

2.2 JVM参数

我们设置下堆内存的总大小为5MB:-Xms5m -Xmx5m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./,这样就可以快速触发堆内存溢出。程序执行后的打印日志输出如下:

复制代码
    当前创建了第160063个对象
    当前创建了第160064个对象
    当前创建了第160065个对象
    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to ./\java_pid6412.hprof ...
    Heap dump file created [7250823 bytes in 0.058 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:3210)
        at java.util.Arrays.copyOf(Arrays.java:3181)
        at java.util.ArrayList.grow(ArrayList.java:261)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
        at java.util.ArrayList.add(ArrayList.java:458)
        at com.mx.jvm.Demo3.main(Demo3.java:12)

可以看到,在5MB的堆内存中,不断创建Obejct对象,当创建到第160065个对象时,堆内存实在放不下了,从而引发java.lang.OutOfMemoryError

三、问题分析

我们通过程序日志中的报错"java.lang.OutOfMemoryError: Java heap space",知道发生了Java堆内存溢出。针对Java堆内存溢出,一般不用看GC日志,因为堆内存溢出伴随的GC日志内容会非常多,我们直接分析dump出的内存快照即可。

采用MAT打开内存快照java_pid6412.hprof(进程名不同,文件名也略有区别):

可以看到一大堆对象占据了81.19%的堆内存,我们直接点击"See stacktrace",看看这些对象是在程序哪里创建出来的:

很显然,main方法的第12行,一直调用list.add(new Object()),由此直接引发了内存溢出,我们只要有针对性的修复我们代码的bug即可。

四、总结

本章,我们通过代码示例模拟了堆内存溢出的场景,Java堆内存也是最容易出现内存溢出的区域。基本的分析思路就是dump出事发现场的内存快照,然后通过MAT进行查看,分析出内存占用最多的对象,然后分析线程调用栈,找到代码位置,最后进行优化即可。

从下一章开始,我们将给出真实的生产环境案例,看看这些案例是如何引起OOM以及排查和解决的思路方法。

相关推荐
没有bug.的程序员11 小时前
Async Profiler:最精准的火焰图工具
java·jvm·spring·对象分配·async profiler
小帅学编程11 小时前
JVM学习记录
jvm·学习
Yweir12 小时前
Linux性能监控的工具集和分析命令工具
java·linux·jvm
友莘居士12 小时前
深入浅出:以太坊虚拟机(EVM)存储模型设计与权衡
jvm·区块链·虚拟机·solidity·evm·合约调用
找不到、了14 小时前
栈帧四要素:JVM 方法执行的完整上下文
java·jvm
TroubleBoy丶14 小时前
Docker可用镜像
java·linux·jvm·docker
Zzzzzxl_15 小时前
互联网大厂Java/Agent面试实战:Spring Boot、JVM、微服务与AI Agent/RAG场景问答
java·jvm·spring boot·ai·agent·rag·microservices
未若君雅裁15 小时前
JVM高级篇总结笔记
java·jvm·笔记
Zzzzzxl_16 小时前
互联网大厂Java/Agent面试实战:JVM、Spring Boot、微服务与RAG全栈问答
java·jvm·springboot·agent·rag·microservices·vectordb
Zzzzzxl_16 小时前
互联网大厂Java/Agent面试实战:微服务、RAG与Agent化实战(含答疑解析)
java·jvm·spring boot·agent·milvus·rag·microservices