工作中经常会遇到这样的报错日志:你是不是不知所措,不知道从哪里定位问题?
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文件,可以看到如下信息:
可以看到,该程序创建了大量的Object对象,占用了大量的内存空间。
接下来,我们可以查看对象的引用链,找到是哪个方法或者代码块创建了这些对象:
可以看到,是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参数、分析堆内存转储文件和使用代码分析工具。通过这些方法和工具可以快速定位和解决内存溢出问题,提高服务的稳定性和可靠性。