1. 高内存占用
线上故障排查(一)——高CPU占用一文中介绍了高 CPU 占用程序的排查,今天我们介绍高MEM占用的程序。高内存占用的程序是指内存消耗比较大的程序。我们在开发 Java 应用程序的时候,一定见过 OOM(即 java.lang.OutOfMemoryError)。在 JVM 内存模型中,Java 虚拟机栈、本地方法栈、Java 堆、方法区(JDK8 之后是元空间,取消了永久代)都会发生 OOM。
对于 OOM 的解决方案不是本文要阐述的,但是如果能定位程序中哪里导致内存使用过多,也可以从一定层面避免 OOM。
2. 线上排查
同上一篇一样,我主要介绍在服务器上,如何使用 Linux 和 Java 命令去定位导致内存使用过多的原因。以下面程序为例:
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Memory {
public static void main(String[] args){
Executor executor = Executors.newFixedThreadPool(5);
Task task1 = new Task();
Task task2 = new Task();
executor.execute(task1);
executor.execute(task2);
}
}
class Task implements Runnable{
@Override
public void run() {
synchronized (Runnable.class){
compute();
}
}
private void compute(){
try {
while (true) {
byte[] bytes = new byte[1024 * 1024 * 500];
Thread.sleep(1000 * 10);
}
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
}
示例程序很简单,循环创建 byte 数组来占用内存。
2.1 使用 top 查看状态
毋庸置疑,查看机器资源的时候,我们首先想到的是 top 命令。对于示例程序,运行之后,不会立即看到内存使用飙升,可能需要等一会儿才可以看到内存的使用比较大,如:
可以看到 70912 进程的内存使用率达到了 7.4%。
2.2 jmap 查看内存信息
之前我们用 ps 定位过线程 CPU 的使用情况,这里我们先用 ps 再看看该进程中的线程信息:
可以看到,ps 查不到指定进程中线程使用内存的任何信息。
但是,JDK 的内置工具中有一个 jmap(Java Memory Map) 命令可以查看 Java 进程的内存信息,这里用两个参数可以使用:
- jmap -dump:[live,]format=b,file=[filename] PID:使用 hprof 二进制形式,输出 JVM 的 Heap 内容到文件中。live 子选项是可选的,假如指定 live 选项,那么只输出活的对象到文件。导出的文件,可以用专门的内存分析工具来分析,如 Eclipse Memory Analyzer Tool(MAT)。
- jmap -histo[:live] PID:打印每个 Class 的实例数目,内存占用,类全名信息。JVM 的内部类名字开头会加上前缀 *(星号)。如果 live 子参数加上后,只统计活的对象数量。
我们这里只讨论在服务器上定位,使用命令 jmap -histo:live 70912 > mem.log 命令将该进程的内存信息导入到 mem.log 文件中,如:
这里简单介绍几个 class name:
- [C:指 char[]。Java 中 String 内部使用 final char[] 来保存数据的。
- [S:指 short[]。
- [I:指 int[]。
- [B:指 byte[]。
- [[I: 指 int[][]。
从导出的文件中,我们发现 [B 占用的内存比较最多,也就是 byte[] 数组占用的内存最多。然后,我们通过定位示例程序中哪里创建了过多的 byte[] 数组,不难发现是第 26 行代码。