文章目录

  • 前言
  • 一、内存溢出情景模拟
  • 1.启动参数设置自动导出
  • 2.java Visual VM
  • 3.运行期手动导出
  • 二、快照分析工具
  • 1.Jvisualvm
  • 2.MAT分析
  • 总结



前言

记录这篇文章的目的是在工作中遇到实际的内存溢出异常时,能根据异常的提示信息迅速得知是哪个区域的内存溢出,知道怎样的代码可能会导致这些区域内存溢出,以及出现这些异常后该如何处理。首先在这里是默认大家熟悉JVM内存模型和GC相关知识的。下面我们会简单模拟一下堆内存溢出的场景并演示如何使用工具进行分析。

一、内存溢出情景模拟

/**
 * @author haichi
 * @version 1.0
 */
public class HeapOOM {

    static class OOMObject {}

    public static void main(String[] args) throws InterruptedException {

        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println(maxMemory);
        System.out.println(totalMemory);

        List<OOMObject> oomObjectList = new ArrayList<>();
        while (true){
            TimeUnit.MILLISECONDS.sleep(10);
            oomObjectList.add(new OOMObject());
        }
    }
}

1.启动参数设置自动导出

首先Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。知道这点就好办了,我们只需要模拟出一个不断new对象且不会被GC的场景即可。满足这个条件的同时我们需要手动设置堆大小给定一个较小的值这样方面我们模拟与观察。首先我们在idea的JVM参数设置中给定一个合适的值,这里我给到了20m。同时 -XX:+HeapDump OnOutOfMemoryError -XX:HeapDump Path=F:\dump 前面参数是指定JVM在内存溢出的情况下自动导出出我们的内存快照,而后面的参数即是我指定了特定的本地路径。

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_JVM

2.java Visual VM

当然除了这种方式我们还可以有其它的选择,比如使用jdk自带的java Visual VM工具

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jdk_02


接下来就会自动为我们导出快照到指定文件

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jvm_03

3.运行期手动导出

jmap -dump:format=b,file=<dumpfile.hprof> <pid>

二、快照分析工具

1.Jvisualvm

直接双击打开jvisualvm.exe,点击文件->装入,在文件类型那一栏选择堆,选择要分析的dump文件,打开

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_JVM_04


Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_JVM_05


Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jdk_06


结合

代码如下(示例):

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2.MAT分析

下载地址:
http://www.eclipse.org/mat/downloads.php 相对前一个java自带的分析工具MAT更加智能直观一点,首先点击 Leak Suspects 来看一下

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jvm_07

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jvm_08


点击 Histogram 来看一下列出每个class产生了多少个实例,以及占有多大内存,所占百分比。明显可以看到光OOMObject对象就占用了90以上的堆内存溢出的问题也就大致锁定了方向

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jdk_09


和Histogram类似,时间久了,通过多次对比也可以把溢出对象找出来,Dominator Tree和Histogram的区别是站的角度不一样,Histogram是站在类的角度上去看,Dominator Tree是站的对象实例的角度上看,Dominator Tree可以更方便的看出其引用关系。

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_jvm_10


通 过Histogram视图或者Dominator Tree视图,找到疑似溢出的对象或者类后,选择Path to GC Roots或者Merge Shortest Paths to GC Roots,这里有很多过滤选项,一般来讲可以选择exclude all plantom/weak/soft etc. references。这样就排除了虚引用、弱引用、以及软引用,剩下的就是强引用。从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存溢出了。

Docker java jvm OOM内存溢出问题排查 诊断监控 jvm内存溢出故障排查_内存溢出_11

总结

jvm调优这方面经验还是很浅薄,工具使用的也不是很熟练。还需要继续沉淀加强自己的经验,后续会对这篇文章进行相应的追加和修改现在看起来还很粗糙。