21年底的时候在CQ项目上就遇到项目上的应用偶发性的自动重启问题。当时经过排查,发现是由于Full GC时间过长触发了wapper的心跳检测超时机制,强制进行了重启操作。至于Full GC时间过长的原因,当时只是怀疑安全扫描软件占用了大量CPU资源,导致了Full GC无法获取到足够的CPU资源而耗时过长。并且这是一个偶发问题,在项目驻场的时间内并没有遇到重启问题。最后,只能先按照安全扫描软件的原因结束排查。但是,这个问题其实一直存在着,并困扰着项目组。中间一段时间我怀疑是Java 8默认的GC算法(Parallel Scavenge + Serial Old)在大内存时表现不佳,让项目组换成了G1算法。

最近,项目上又找到我,反馈又有其他应用出现了自动重启的问题。排查GCLog后,确认还是由于Full GC耗时过长问题。在网上搜索时,发现了https://developer.jdcloud.com/article/2687这篇文章,于是按照这个思路排查了一遍,发现确实发生问题的服务器上swap分区空间基本被占满了。并且,统计了占用swap分区空间最多的前10个进程中就有我们的java进程。另外,占用swap分区空间最多的是安全扫描软件。从这些现象推测,CQ项目上大概率也是因为swap分区的问题。

为了尝试复现此问题,我本地写了测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.Scanner;

public class App {

    static int[] buff;

    public static void main(String[] args) throws InterruptedException {
        Scanner scanner = new Scanner(System.in);
        buff = new int[1024 * 1024 * 200];
        for (int i = 0; i < buff.length; i++) {
            buff[i] = i;
        }
        while (true) {
            System.out.println("增加内存占用:");
            if (scanner.hasNext()) {
                String str = scanner.next();
                if (str == null || "".equals(str)) {
                    continue;
                }
                if ("q".equals(str)) {
                    break;
                }
                if (str.contains("|")) {
                    String[] ss = str.split("\\|");
                    int count = Integer.parseInt(ss[0]);
                    int sleep = Integer.parseInt(ss[1]);
                    for (int sc=0;sc<sleep;sc++) {
                        for (int c = 1; c <= count; c++) {
                            int[] largeArray = new int[1024 * 1024 * 10 * c];
                            for (int i = 0; i < largeArray.length; i++) {
                                largeArray[i] = i;
                            }
                        }
                        Thread.sleep(1000);
                    }
                } else {
                    int count = Integer.parseInt(str);
                    for (int c = 1; c <= count; c++) {
                        int[] largeArray = new int[1024 * 1024 * 10 * c];
                        for (int i = 0; i < largeArray.length; i++) {
                            largeArray[i] = i;
                        }
                    }
                }
                System.out.println("内存已增加");
            }
        }
    }
}

然后使用命令行启动:

1
java -server -Xms3350m -Xmx6144m -verbose:gc -Xloggc:./gc-%t.log -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof App

使用jstat -gcutil pid 1000进行监控。

后面想尝试通过systemtap模拟磁盘抖动或延迟