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以及排查和解决的思路方法。

相关推荐
学点东西吧.13 小时前
JVM(一、基础知识)
java·jvm
XMYX-013 小时前
JVM 参数配置入门与优化案例
jvm
努力进修15 小时前
“高级Java编程复习指南:深入理解并发编程、JVM优化与分布式系统架构“
java·jvm·架构
秋の花16 小时前
【JAVA基础】JVM双亲委派
java·开发语言·jvm
customer0818 小时前
【开源免费】基于SpringBoot+Vue.JS医疗病历交互系统(JAVA毕业设计)
java·jvm·vue.js·spring boot·后端·spring cloud·kafka
Enoch88818 小时前
Day09 C++ 存储类
java·jvm·c++
YYYYYY0202021 小时前
C++中级学习笔记
jvm·笔记·学习
花心蝴蝶.1 天前
并发编程中常见的锁策略
java·jvm·windows
白总Server1 天前
JVM 处理多线程并发执行
jvm·后端·spring cloud·微服务·ribbon·架构·数据库架构
菜菜-plus1 天前
监控JVM的内存使用情况分析
jvm