java大对象引发的频繁full gc及cpu飙高的调优历程

问题描述

linux系统,执行top命令

-bash-4.2# top

top - 08:57:47 up 147 days, 23:08,  2 users,  load average: 67.32, 60.83, 58.97
Tasks: 1310 total,   2 running, 1307 sleeping,   0 stopped,   1 zombie
%Cpu(s): 48.9 us,  1.2 sy,  0.0 ni, 49.6 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 56109344+total,  1076992 free, 49768768 used, 51024771+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 50798355+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 70853 root      20   0 71.947g 0.031t  20088 S  6978  6.0  11566:28 java
 30632 root      20   0 43.589g 1.229g  12108 S  68.9  0.2   9335:55 java
  2135 root      20   0  160600  46492    832 R  13.7  0.0   4648:46 statmon
  4219 root      20   0 99.287g 2.030g  11576 S   2.9  0.4 747:32.73 java
 59325 root      20   0  158752   3552   1588 R   2.5  0.0   0:00.70 top
 57806 root       0 -20       0      0      0 S   1.9  0.0   0:01.70 kworker/18:2H
    10 root      20   0       0      0      0 S   0.6  0.0 313:00.57 rcu_sched
 129997 root      20   0 44.030g 3.102g  11992 S   0.6  0.6   2840:59 java 

上述70853这个进程,占用过高的cpu,而且程序运行缓慢,重启之后就正常了,运行一段时间之后又是这样。

问题排查

线程使用cpu排查

首先遇到cpu彪高的问题,我们要先定位是哪些线程在持有这些cpu,采用top -Hp pid命令可以查看线程的cpu使用情况

-bash-4.2# top -Hp 70853

top - 09:03:01 up 147 days, 23:13,  2 users,  load average: 39.88, 48.75, 54.03
Threads: 601 total,  93 running, 508 sleeping,   0 stopped,   0 zombie
%Cpu(s): 43.2 us, 11.3 sy,  0.0 ni, 45.0 id,  0.3 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 56109344+total,   878212 free, 49768532 used, 51044672+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 50798249+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
 70866 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  79:06.89 java
 70906 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  78:58.43 java
 70921 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  78:58.79 java
 70856 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:01.84 java
 70859 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  78:57.72 java
 70863 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:06.32 java
 70865 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:09.23 java
 70872 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  78:52.49 java
 70875 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:02.85 java
 70877 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:04.04 java
 70879 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:07.52 java
 70881 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:00.38 java

上述是一系列的该进程下的线程使用cpu的情况,并且省略了一部分,将上述pid下面的十进制的线程id转成16进制,如下:

-bash-4.2# printf "%x\n" 70856
114c8

线程堆栈排查

上述排查到了哪些线程使用cpu率高,那么这些cpu到底在干什么呢?可以使用jstack pid命令查看jvm的线程堆栈

 

gc动态监控

上述线程堆栈查看,发现线程大部分在执行gc回收,这就奇怪了,为什么一直在gc,具体查看gc的情况,可以使用jstat -gcutil pid

-bash-4.2# jstat -gcutil 70853 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  99.90  99.98  92.68  88.47  11534 1351.766  2466 4811.444 6163.210
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2467 4813.394 6165.160
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2467 4813.394 6165.160
  0.00   0.00  99.96 100.00  92.68  88.47  11534 1351.766  2468 4815.426 6167.192
  0.00   0.00  99.96 100.00  92.68  88.47  11534 1351.766  2468 4815.426 6167.192
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2469 4817.455 6169.221
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2469 4817.455 6169.221
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2470 4819.330 6171.096
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2470 4819.330 6171.096
  0.00   0.00 100.00 100.00  92.68  88.47  11534 1351.766  2471 4821.328 6173.094
  0.00   0.00 100.00 100.00  92.68  88.47  11534 1351.766  2471 4821.328 6173.094
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2472 4823.229 6174.995
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2472 4823.229 6174.995
  0.00   0.00  99.97  99.97  92.68  88.47  11534 1351.766  2473 4825.121 6176.888
  0.00   0.00  99.97  99.97  92.68  88.47  11534 1351.766  2473 4825.121 6176.888
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2474 4827.092 6178.859
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2474 4827.092 6178.859
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2475 4829.041 6180.807
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2475 4829.041 6180.807 

S0,S1:新生代的两个幸存者区

E:新生代的伊甸园区

O:老年代

YGC:新生代gc

FGC:full gc 

上述主要指标解释

S0,S1:新生代的两个幸存者区

E:新生代的伊甸园区

O:老年代

YGC:新生代gc

FGC:full gc 

通过上述排查,可以发现,一直在full gc,并且老年代内存并没有回收,致此,内存泄露了。

堆内存查看占用内存的对象

采用jvisiual命令查看heap dump

上图所示被ScheduledThreadPoolExecutor中的优先级队列持有。

代码分析

逻辑是这样的,视图库接收前端实卡传来的图片,高清图片,大概有几百kb到十几M不等,采用1400标准接口,内部采用线程池来异步处理传来的图片及结构化数据,线程池代码如下所示:

private ScheduledExecutorService threadPool ;

private BasicService basicService;

@PostConstruct
public void vcnLogin(){
    BasicService basicService = new BasicServiceImpl();
    basicService.setLogPath("/opt/");
    int result = basicService.initial();
    int result1 = basicService.login("vcnesdk02", "Huawei12#$", "34.8.20.132", 9900);
    logger.info("vcn login result: "+result1);

    threadPool = Executors.newScheduledThreadPool(50);
}

 来的图ScheduledThreadPoolExecutor线程池默认采用DelayedWorkQueue,是一种延时的优先级队列,在这里需要关注的是无界队列。

项目刚开始运行没什么问题,在早高峰和晚高峰的时候,数据突然多了起来,full gc发生频繁,因为采用的并行GC算法,占用资源过多,导致线程池处理越来越慢,队列积压的任务越来越多,老年代空间越来越小,造成full gc越来越频繁,gc越频繁,任务处理越慢,最终导致上图所示的老年代都满了,但是full gc之后,空间一直不下降。数据接入曲线图如下:

图中显示早高峰7点半的时候,达到了数据洪峰,系统扛不住了,于是老年代满了,频繁full gc,导致系统越来越慢,吞吐量下降。

解决方案

略,随意。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章