工作中经常会遇到这样的报错日志:你是不是不知所措,不知道从哪里定位问题? 

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.util.Arrays.copyOf(Arrays.java:3298)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at MemoryLeakDemo.run(MemoryLeakDemo.java:12)
	at MemoryLeakDemo.main(MemoryLeakDemo.java:20)

 这是对象创建不当,导致的内存溢出。从而导致服务频繁异常。

定位内存溢出的流程

以下是一般情况下定位Java服务内存溢出的流程:

1. 确认是否是内存溢出

首先,我们需要确认是否是内存溢出导致的问题。可以查看Java服务的日志文件或者控制台输出,如果出现了类似于“OutOfMemoryError”这样的错误信息,那么就可以确认是内存溢出了。

2. 获取堆栈跟踪信息

在发生内存溢出时,JVM会自动生成一个dump文件,我们可以使用工具(如jmap、jstack)来获取该文件中的堆栈跟踪信息。堆栈跟踪信息可以告诉我们是哪个方法或者代码块导致了内存溢出。

3. 分析dump文件

获取到堆栈跟踪信息后,我们需要分析dump文件,确定哪些对象占用了过多的内存空间。可以使用工具(如MAT、VisualVM)来分析dump文件。

4. 优化代码

通过分析dump文件,我们可以确定是哪个方法或者代码块导致了内存溢出。接下来,我们就可以优化代码,减少对象的创建,释放不必要的资源等等。

定位内存溢出的示例

以下是一个简单的Java程序,用于演示内存溢出的定位和排查过程。

import java.util.ArrayList;
import java.util.List;

public class MemoryLeakDemo {

    private List<Object> list = new ArrayList<>();

    public void run() {
        while (true) {
            list.add(new Object());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MemoryLeakDemo demo = new MemoryLeakDemo();
        demo.run();
    }
}

该程序会不断地向一个List中添加Object对象,最终导致内存溢出。下面是定位内存溢出的流程:

1. 确认是否是内存溢出

运行程序后,控制台输出了如下信息:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.util.Arrays.copyOf(Arrays.java:3298)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at MemoryLeakDemo.run(MemoryLeakDemo.java:12)
	at MemoryLeakDemo.main(MemoryLeakDemo.java:20)

可以看到,程序发生了内存溢出。

2. 获取堆栈跟踪信息

使用jstack命令获取堆栈跟踪信息:

$ jstack -l 12345 > dump.txt

其中,12345是Java服务的进程ID。

3. 分析dump文件

使用MAT工具打开dump文件,可以看到如下信息:

java 内存逃逸是什么 java内存溢出定位_jvm

 

可以看到,该程序创建了大量的Object对象,占用了大量的内存空间。

接下来,我们可以查看对象的引用链,找到是哪个方法或者代码块创建了这些对象:

java 内存逃逸是什么 java内存溢出定位_开发语言_02

 

java 内存逃逸是什么 java内存溢出定位_java 内存逃逸是什么_03

 

可以看到,是MemoryLeakDemo类的run方法创建了这些对象。

4. 优化代码

根据分析结果,我们可以确定是run方法导致了内存溢出。接下来,我们可以优化代码,例如限制List的大小,释放不必要的资源等等。

import java.util.ArrayList;
import java.util.List;

public class MemoryLeakDemo {

    private List<Object> list = new ArrayList<>();
    private int maxSize = 100000;

    public void run() {
        while (true) {
            if (list.size() >= maxSize) {
                list.clear();
            }
            list.add(new Object());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MemoryLeakDemo demo = new MemoryLeakDemo();
        demo.run();
    }
}

通过限制List的大小,我们可以避免内存溢出的问题。

如何定位内存溢出

内存溢出是指在程序运行过程中,申请的内存超过了系统能够分配的内存大小,导致程序崩溃或异常退出。对于Java服务而言,内存溢出是比较常见的问题之一。下面是定位Java服务内存溢出的一些方法和步骤。

1. 使用JVM参数启动服务

为了能够定位内存溢出问题,我们需要在启动服务时加入一些特定的JVM参数,以便在程序出现内存溢出时能够生成相应的日志信息。以下是一些常用的JVM参数:

  • -Xmx:设置最大堆内存大小
  • -Xms:设置堆内存初始大小
  • -XX:MaxPermSize:设置最大永久代内存大小
  • -XX:+HeapDumpOnOutOfMemoryError:当程序出现内存溢出时,生成堆内存转储文件
  • -XX:HeapDumpPath:设置堆内存转储文件的保存路径

2. 分析堆内存转储文件

当Java服务出现内存溢出问题时,可以通过分析堆内存转储文件来定位问题。堆内存转储文件是在JVM参数-XX:+HeapDumpOnOutOfMemoryError设置为true时生成的。以下是分析堆内存转储文件的一些工具:

  • Eclipse Memory Analyzer Tool (MAT):MAT是一款开源的Java内存分析器,可以分析堆内存转储文件,查找内存泄漏和内存溢出问题。
  • VisualVM:VisualVM是一款免费的Java性能分析工具,可以分析堆内存转储文件,查找内存泄漏和内存溢出问题。
  • jhat:jhat是JDK自带的一个命令行工具,可以将堆内存转储文件转换成HTML格式,便于查找内存泄漏和内存溢出问题。

3. 使用代码分析工具

除了分析堆内存转储文件外,还可以使用一些代码分析工具来查找内存泄漏和内存溢出问题。以下是一些常用的代码分析工具:

  • FindBugs:FindBugs是一款开源的静态代码分析工具,可以查找Java代码中的潜在缺陷和问题,包括内存泄漏和内存溢出问题。
  • PMD:PMD是一款开源的静态代码分析工具,可以查找Java代码中的潜在缺陷和问题,包括内存泄漏和内存溢出问题。
  • Checkstyle:Checkstyle是一款开源的静态代码分析工具,可以查找Java代码中的潜在缺陷和问题,包括内存泄漏和内存溢出问题。

4. 总结

定位Java服务内存溢出问题需要使用多种方法和工具,包括设置JVM参数、分析堆内存转储文件和使用代码分析工具。通过这些方法和工具可以快速定位和解决内存溢出问题,提高服务的稳定性和可靠性。