乐趣区

模拟实战排查堆内存溢出javalangOutOfMemoryError-Java-heap-space问题

前言:

模拟实战中排查堆内存溢出(java.lang.OutOfMemoryError: Java heap space)的问题。

堆内存溢出的原因:一般都是创建了大量的对象,这些对象一直被引用着,无法被 GC 垃圾回收掉,最终导致堆内存被占满,没有足够的空间存放新创建的对象时,就会出现堆内存溢出问题。

在实际的业务场景中出现内存溢出的问题,排查起来一般是十分困难繁琐的,本文将通过结合一个简单的实例来阐述排查的具体思路和步骤。

准备:

注意:在实际场景中,一般都是部署在 Linux 服务器中的项目报出内存溢出的问题;为了尽可能还原出实际场景,本文也是将提前编写好的可以触发内存溢出的代码并打包成可运行的 Jar 包,然后放到服务器中执行的。

1、准备可导致内存溢出的代码:

// 创建一个 Java 类
public class OutOfMemory {
    
    private String test;
    
    public OutOfMemory(String test){this.test = test;}
    
}

// 模拟内存溢出的发生
public class TestOOM {public static void main(String[] args) {List<OutOfMemory> list = new ArrayList<OutOfMemory>();
        
        while(true){
            /**
             * 无限创建 OutOfMemory 对象,直至将堆空间占满,并且创建的 OutOfMemory 对象一直被 list 集合对象引用着,* 导致 GC 也无法回收,最终出现堆内存溢出问题
             */
            list.add(new OutOfMemory("5656"));
            System.out.println("5656");
        }
    }
}

代码编写完成后,使用开发工具导出 可运行的 Jar 包(TestOOM.jar)

2、准备 Linux 服务器

可以直接使用 centos 或者 Red Hat 等都可以;

实战:

1、将可运行的 Jar 包放到服务器中执行:

①、可使用 xshell、xftp 工具将可运行的 Jar 包(Jar 包叫:TestOOM.jar)放入到服务器中;
②、使用命令执行 Jar 包;命令:
     java -Xms40m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/tmp   -jar TestOOM.jar

注意:为了尽快模拟发生堆内存溢出,所以在启动 Jar 包时,设置了一些参数;参数解析:
     1)、-Xms40m 初始堆大小设置为 40m
     2)、-Xmx70m 最大堆大小设置为 70m
     3)、-XX:+HeapDumpOnOutOfMemoryError 出现堆内存溢出时,自动导出堆内存 dump 快照
     4)、-XX:HeapDumpPath=/usr/tmp 设置导出的堆内存快照的存放地址为 /usr/tmp

2、执行成功后,使用 JVM 监控命令监控 JVM 的信息:

①、jps 命令:此命令是用来查询与 Java 相关的进程的,并输出进程号;下图就是展示上面运行的 Jar 包的进程号:

②、jmap 命令:jmap -heap 3324  此命令是查询出进程号为 3324 的 JVM 中堆内存信息;如下图:

在图中可以发现堆内存中新生代、年老代中 free 可用空间越来越小,这预示着即将会发生 GC 垃圾回收,从而使堆腾出更多的空间存放新创建的对象。

③、jstat 命令:使用其监控 JVM 的性能信息;例如:在本次排查内存溢出的问题中,会使用 jstat 命令监控 JVM 的 GC 垃圾回收的情况;
  命令:jstat -gcutil 3324 1000   意思是每 1000 毫秒查询一次进程号为 3324 的 JVM 的 GC 垃圾回收的情况;如下图:

(1)、YGC(堆中新生代 GC)、FGC(FULL GC)为什么触发频率这么快呢?
答:由于堆内存空间不够用了,需要通过 GC 垃圾回收将一些空间进行回收,用于存放新创建的对象。

(2)、当堆内存空间不够用时,GC 具体会发生什么呢?
答:
1)、当堆中的新生代空间不够用时,会触发 YGC,对堆中新生代空间进行垃圾回收,同时垃圾回收后剩余存活的对象会移动到堆中
老年代存储,所以每次 YGC 后,堆中年老代中存储的对象数量会增大;

2)、当堆的新生代即将发生 YGC 时,如果发现新生代中存活下来的对象比堆中年老代中剩余的可用空间大的话,就会直接不进行 YGC,
而会直接触发 FGC,FULL GC 会对整个堆空间(新生代、老年代)以及方法区 / 永久代进行垃圾回收;

扩展:堆的结构图:

3、出现内存溢出后,会自动生成快照,然后分析堆内存快照:

①、使用 XFTP 等工具将服务器中的快照文件导出,本文堆内存快照文件是以 hprof 为后缀的文件;导出快照文件后,可以通过 JDK 自带的 jvisualvm.exe 分析工具打开进行分析。

jvisualvm.exe 是在哪里呢?(以 windows 系统为例)
它是在 JDK 的安装目录中的 bin 目录下的

如图:

②、使用 jvisualvm.exe 导入快照文件,如图:
(1)、

(2)、

(3)、

通过分析堆内存快照得到的结论:
通过第(3)张图,可以发现堆内存中有一个实例对象的占比为 99.9%,可以确定是由于这个实例对象大量创建导致堆内存的溢出;
说到这,可以回过头去看下我们自己编写的可以触发堆内存溢出的小程序,发现正是由于在 while(true) 死循环 中无线创建 OutOfMemory 对象,导致堆内存空间被耗尽。

结语:
通过上面的实战小例子,我们可以大体了解到在出现堆内存溢出时的排查步骤,但是在实际的场景中,这种情况可能会更加的复杂多变;

比如说,上面的那个小例子在出现的堆内存溢出时自动生成的堆内存快照文件大小就达到了 100 多 m,如果在实际的场景中,这个可能是非常巨大的,这时可能就会发生快照分析工具无法导入堆内存快照。所以说,我们需要在平时通过不断的学习,才能在未来出现问题时,能尽快定位问题并解决问题;程序员不光是能编写好代码,还需要有解决问题的能力。

❤不要忘记留下你学习的足迹 [点赞 + 收藏 + 评论]嘿嘿ヾ

一切看文章不点赞都是“耍流氓”,嘿嘿ヾ (◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每个人出一份力量(点赞 + 评论) 就会让更多的学习者加入进来!非常感谢!~ω~=

退出移动版